mirror of
https://github.com/github/codeql.git
synced 2025-12-17 09:13:20 +01:00
1875 lines
67 KiB
Plaintext
1875 lines
67 KiB
Plaintext
/**
|
|
* Provides an implementation of _API graphs_, which are an abstract representation of the API
|
|
* surface used and/or defined by a code base.
|
|
*
|
|
* See `API::Node` for more in-depth documentation.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
|
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
|
private import semmle.javascript.dataflow.internal.StepSummary
|
|
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
|
|
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
|
|
private import semmle.javascript.DynamicPropertyAccess
|
|
private import internal.CachedStages
|
|
|
|
/**
|
|
* Provides classes and predicates for working with the API boundary between the current
|
|
* codebase and external libraries.
|
|
*
|
|
* See `API::Node` for more in-depth documentation.
|
|
*/
|
|
module API {
|
|
/**
|
|
* A node in the API graph, representing a value that has crossed the boundary between this
|
|
* codebase and an external library (or in general, any external codebase).
|
|
*
|
|
* ### Basic usage
|
|
*
|
|
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
|
* whose implementation is not necessarily part of the current codebase.
|
|
*
|
|
* The most basic use of API graphs is typically as follows:
|
|
* 1. Start with `API::moduleImport` for the relevant library.
|
|
* 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function.
|
|
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
|
*
|
|
* For example, a simplified way to get arguments to `underscore.extend` would be
|
|
* ```ql
|
|
* API::moduleImport("underscore").getMember("extend").getParameter(0).asSink()
|
|
* ```
|
|
*
|
|
* The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`.
|
|
*
|
|
* ### API graph nodes
|
|
*
|
|
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
|
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
|
* (The current codebase is "using" a value that came from the library).
|
|
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
|
* (The current codebase "defines" the value seen by the library).
|
|
*
|
|
* API graph nodes are associated with data-flow nodes in the current codebase.
|
|
* (Since external libraries are not part of the database, there is no way to associate with concrete
|
|
* data-flow nodes from the external library).
|
|
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
|
* such as the return value of a call to an external function.
|
|
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
|
* such as an argument passed in a call to an external function.
|
|
*
|
|
*
|
|
* ### Access paths and edge labels
|
|
*
|
|
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
|
* that may be performed to obtain that value.
|
|
*
|
|
* For example, the access path `API::moduleImport("lodash").getMember("extend")` represents the action of
|
|
* importing `lodash` and then accessing the member `extend` on the resulting object.
|
|
* It would be associated with an expression such as `require("lodash").extend`.
|
|
*
|
|
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
|
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
|
* the result:
|
|
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
|
* came from a library.
|
|
* - An edge starting from a def-node describes what the external library might do to a value that
|
|
* came from the current codebase.
|
|
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
|
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
|
* the place where it was "last seen" in the current codebase before flowing out)
|
|
*
|
|
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
|
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
|
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
|
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
|
*
|
|
* For example, given this snippet:
|
|
* ```js
|
|
* require('foo')(x => { doSomething(x) })
|
|
* ```
|
|
* A callback is passed to the external function `foo`. We can't know if `foo` will actually invoke this callback.
|
|
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
|
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo`.
|
|
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`.
|
|
*
|
|
* ### Thinking in operations versus code patterns
|
|
*
|
|
* Treating edges as "operations" helps avoid a pitfall in which library models become overly specific to certain code patterns.
|
|
* Consider the following two equivalent calls to `foo`:
|
|
* ```js
|
|
* const foo = require('foo');
|
|
*
|
|
* foo({
|
|
* myMethod(x) {...}
|
|
* });
|
|
*
|
|
* foo({
|
|
* get myMethod() {
|
|
* return function(x) {...}
|
|
* }
|
|
* });
|
|
* ```
|
|
* If `foo` calls `myMethod` on its first parameter, either of the `myMethod` implementations will be invoked.
|
|
* And indeed, the access path `API::moduleImport("foo").getParameter(0).getMember("myMethod").getParameter(0)` correctly
|
|
* identifies both `x` parameters.
|
|
*
|
|
* Observe how `getMember("myMethod")` behaves when the member is defined via a getter. When thinking in code patterns,
|
|
* it might seem obvious that `getMember` should have obtained a reference to the getter method itself.
|
|
* But when seeing it as an access to `myMethod` performed by the library, we can deduce that the relevant expression
|
|
* on the client side is actually the return-value of the getter.
|
|
*
|
|
* Although one may think of API graphs as a tool to find certain program elements in the codebase,
|
|
* it can lead to some situations where intuition does not match what works best in practice.
|
|
*/
|
|
class Node extends Impl::TApiNode {
|
|
/**
|
|
* Get a data-flow node where this value may flow after entering the current codebase.
|
|
*
|
|
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
|
* See `asSource()` for examples.
|
|
*/
|
|
pragma[inline]
|
|
DataFlow::Node getAValueReachableFromSource() {
|
|
Impl::trackUseNode(this.asSource()).flowsTo(result)
|
|
}
|
|
|
|
/**
|
|
* Get a data-flow node where this value enters the current codebase.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* // API::moduleImport("fs").asSource()
|
|
* require('fs');
|
|
*
|
|
* // API::moduleImport("fs").getMember("readFile").asSource()
|
|
* require('fs').readFile;
|
|
*
|
|
* // API::moduleImport("fs").getMember("readFile").getReturn().asSource()
|
|
* require('fs').readFile();
|
|
*
|
|
* require('fs').readFile(
|
|
* filename,
|
|
* // 'y' matched by API::moduleImport("fs").getMember("readFile").getParameter(1).getParameter(0).asSource()
|
|
* y => {
|
|
* ...
|
|
* });
|
|
* ```
|
|
*/
|
|
DataFlow::SourceNode asSource() { Impl::use(this, result) }
|
|
|
|
/**
|
|
* Gets a call to the function represented by this API component.
|
|
*/
|
|
CallNode getACall() { result = this.getReturn().asSource() }
|
|
|
|
/**
|
|
* Gets a call to the function represented by this API component,
|
|
* or a promisified version of the function.
|
|
*/
|
|
CallNode getMaybePromisifiedCall() {
|
|
result = this.getACall()
|
|
or
|
|
result = Impl::getAPromisifiedInvocation(this, _, _)
|
|
}
|
|
|
|
/**
|
|
* Gets a `new` call to the function represented by this API component.
|
|
*/
|
|
NewNode getAnInstantiation() { result = this.getInstance().asSource() }
|
|
|
|
/**
|
|
* Gets an invocation (with our without `new`) to the function represented by this API component.
|
|
*/
|
|
InvokeNode getAnInvocation() { result = this.getACall() or result = this.getAnInstantiation() }
|
|
|
|
/**
|
|
* Get a data-flow node where this value leaves the current codebase and flows into an
|
|
* external library (or in general, any external codebase).
|
|
*
|
|
* Concretely, this is either an argument passed to a call to external code,
|
|
* or the right-hand side of a property write on an object flowing into such a call.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* // 'x' is matched by API::moduleImport("foo").getParameter(0).asSink()
|
|
* require('foo')(x);
|
|
*
|
|
* // 'x' is matched by API::moduleImport("foo").getParameter(0).getMember("prop").asSink()
|
|
* require('foo')({
|
|
* prop: x
|
|
* });
|
|
* ```
|
|
*/
|
|
DataFlow::Node asSink() { Impl::rhs(this, result) }
|
|
|
|
/**
|
|
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
|
*
|
|
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
|
* See `asSink()` for examples.
|
|
*/
|
|
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
|
|
|
/**
|
|
* Gets a node representing member `m` of this API component.
|
|
*
|
|
* For example, modules have an `exports` member representing their exports, and objects have
|
|
* their properties as members.
|
|
*/
|
|
cached
|
|
Node getMember(string m) {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::member(m))
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
|
|
*/
|
|
deprecated Node getUnknownMember() { result = this.getArrayElement() }
|
|
|
|
/**
|
|
* Gets an array element of unknown index.
|
|
*/
|
|
cached
|
|
Node getUnknownArrayElement() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
|
|
}
|
|
|
|
cached
|
|
private Node getContentRaw(DataFlow::Content content) {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::content(content))
|
|
}
|
|
|
|
/**
|
|
* Gets a representative for the `content` of this value.
|
|
*
|
|
* When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getMember`.
|
|
*/
|
|
pragma[inline]
|
|
Node getContent(DataFlow::Content content) {
|
|
result = this.getContentRaw(content)
|
|
or
|
|
result = this.getMember(content.asPropertyName())
|
|
}
|
|
|
|
/**
|
|
* Gets a representative for the `contents` of this value.
|
|
*/
|
|
bindingset[contents]
|
|
pragma[inline_late]
|
|
private Node getContents(DataFlow::ContentSet contents) {
|
|
// We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph.
|
|
result = this.getContent(contents.getAReadContent())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing an arbitrary array element in the array represented by this node.
|
|
*/
|
|
cached
|
|
Node getArrayElement() { result = this.getContents(DataFlow::ContentSet::arrayElement()) }
|
|
|
|
/**
|
|
* Gets a node representing a member of this API component where the name of the member may
|
|
* or may not be known statically.
|
|
*/
|
|
cached
|
|
Node getAMember() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getMember(_)
|
|
or
|
|
result = this.getUnknownArrayElement()
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing an instance of the class represented by this node.
|
|
* This includes instances of subclasses.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* import { C } from "foo";
|
|
*
|
|
* new C(); // API::moduleImport("foo").getMember("C").getInstance()
|
|
*
|
|
* class D extends C {
|
|
* m() {
|
|
* this; // API::moduleImport("foo").getMember("C").getInstance()
|
|
* }
|
|
* }
|
|
*
|
|
* new D(); // API::moduleImport("foo").getMember("C").getInstance()
|
|
* ```
|
|
*/
|
|
cached
|
|
Node getInstance() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::instance())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing the `i`th parameter of the function represented by this node.
|
|
*
|
|
* This predicate may have multiple results when there are multiple invocations of this API component.
|
|
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
|
*/
|
|
cached
|
|
Node getParameter(int i) {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::parameter(i))
|
|
or
|
|
exists(int spreadIndex, string arrayProp |
|
|
result = this.getASuccessor(Label::spreadArgument(spreadIndex)).getMember(arrayProp) and
|
|
i = spreadIndex + arrayProp.toInt()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the number of parameters of the function represented by this node.
|
|
*/
|
|
int getNumParameter() { result = max(int s | exists(this.getParameter(s))) + 1 }
|
|
|
|
/**
|
|
* Gets a node representing the last parameter of the function represented by this node.
|
|
*
|
|
* This predicate may have multiple results when there are multiple invocations of this API component.
|
|
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
|
*/
|
|
Node getLastParameter() { result = this.getParameter(this.getNumParameter() - 1) }
|
|
|
|
/**
|
|
* Gets a node representing the receiver of the function represented by this node.
|
|
*/
|
|
cached
|
|
Node getReceiver() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::receiver())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing a parameter of the function represented by this node.
|
|
*
|
|
* This predicate may result in a mix of parameters from different call sites in cases where
|
|
* there are multiple invocations of this API component.
|
|
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
|
*/
|
|
cached
|
|
Node getAParameter() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getParameter(_)
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing the result of the function represented by this node.
|
|
*
|
|
* This predicate may have multiple results when there are multiple invocations of this API component.
|
|
* Consider using `getACall()` if there is a need to distingiush between individual calls.
|
|
*/
|
|
cached
|
|
Node getReturn() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::return())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing the promised value wrapped in the `Promise` object represented by
|
|
* this node.
|
|
*/
|
|
cached
|
|
Node getPromised() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::promised())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing the error wrapped in the `Promise` object represented by this node.
|
|
*/
|
|
cached
|
|
Node getPromisedError() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::promisedError())
|
|
}
|
|
|
|
/**
|
|
* Gets a node representing a function that is a wrapper around the function represented by this node.
|
|
*
|
|
* Concretely, a function that forwards all its parameters to a call to `f` and returns the result of that call
|
|
* is considered a wrapper around `f`.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* function f(x) {
|
|
* return g(x); // f = g.getForwardingFunction()
|
|
* }
|
|
*
|
|
* function doExec(x) {
|
|
* console.log(x);
|
|
* return exec(x); // doExec = exec.getForwardingFunction()
|
|
* }
|
|
*
|
|
* function doEither(x, y) {
|
|
* if (x > y) {
|
|
* return foo(x, y); // doEither = foo.getForwardingFunction()
|
|
* } else {
|
|
* return bar(x, y); // doEither = bar.getForwardingFunction()
|
|
* }
|
|
* }
|
|
*
|
|
* function wrapWithLogging(f) {
|
|
* return (x) => {
|
|
* console.log(x);
|
|
* return f(x); // f.getForwardingFunction() = anonymous arrow function
|
|
* }
|
|
* }
|
|
* wrapWithLogging(g); // g.getForwardingFunction() = wrapWithLogging(g)
|
|
* ```
|
|
*/
|
|
cached
|
|
Node getForwardingFunction() {
|
|
Stages::ApiStage::ref() and
|
|
result = this.getASuccessor(Label::forwardingFunction())
|
|
}
|
|
|
|
/**
|
|
* Gets any class that has this value as a decorator.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* import { D } from "foo";
|
|
*
|
|
* // moduleImport("foo").getMember("D").getADecoratedClass()
|
|
* @D
|
|
* class C1 {}
|
|
*
|
|
* // moduleImport("foo").getMember("D").getReturn().getADecoratedClass()
|
|
* @D()
|
|
* class C2 {}
|
|
* ```
|
|
*/
|
|
cached
|
|
Node getADecoratedClass() { result = this.getASuccessor(Label::decoratedClass()) }
|
|
|
|
/**
|
|
* Gets any method, field, or accessor that has this value as a decorator.
|
|
*
|
|
* In the case of an accessor, this gets the return value of a getter, or argument to a setter.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* import { D } from "foo";
|
|
*
|
|
* class C {
|
|
* // moduleImport("foo").getMember("D").getADecoratedMember()
|
|
* @D m1() {}
|
|
* @D f;
|
|
* @D get g() { return this.x; }
|
|
*
|
|
* // moduleImport("foo").getMember("D").getReturn().getADecoratedMember()
|
|
* @D() m2() {}
|
|
* @D() f2;
|
|
* @D() get g2() { return this.x; }
|
|
* }
|
|
* ```
|
|
*/
|
|
cached
|
|
Node getADecoratedMember() { result = this.getASuccessor(Label::decoratedMember()) }
|
|
|
|
/**
|
|
* Gets any parameter that has this value as a decorator.
|
|
*
|
|
* For example:
|
|
* ```js
|
|
* import { D } from "foo";
|
|
*
|
|
* class C {
|
|
* method(
|
|
* // moduleImport("foo").getMember("D").getADecoratedParameter()
|
|
* @D
|
|
* param1,
|
|
* // moduleImport("foo").getMember("D").getReturn().getADecoratedParameter()
|
|
* @D()
|
|
* param2
|
|
* ) {}
|
|
* }
|
|
* ```
|
|
*/
|
|
cached
|
|
Node getADecoratedParameter() { result = this.getASuccessor(Label::decoratedParameter()) }
|
|
|
|
/**
|
|
* Gets a string representation of the lexicographically least among all shortest access paths
|
|
* from the root to this node.
|
|
*/
|
|
string getPath() {
|
|
result = min(string p | p = this.getAPath(Impl::distanceFromRoot(this)) | p)
|
|
}
|
|
|
|
/**
|
|
* Gets a node such that there is an edge in the API graph between this node and the other
|
|
* one, and that edge is labeled with `lbl`.
|
|
*/
|
|
Node getASuccessor(Label::ApiLabel lbl) { Impl::edge(this, lbl, result) }
|
|
|
|
/**
|
|
* Gets a node such that there is an edge in the API graph between that other node and
|
|
* this one, and that edge is labeled with `lbl`
|
|
*/
|
|
Node getAPredecessor(Label::ApiLabel lbl) { this = result.getASuccessor(lbl) }
|
|
|
|
/**
|
|
* Gets a node such that there is an edge in the API graph between this node and the other
|
|
* one.
|
|
*/
|
|
Node getAPredecessor() { result = this.getAPredecessor(_) }
|
|
|
|
/**
|
|
* Gets a node such that there is an edge in the API graph between that other node and
|
|
* this one.
|
|
*/
|
|
Node getASuccessor() { result = this.getASuccessor(_) }
|
|
|
|
/**
|
|
* Holds if this node may take its value from `that` node.
|
|
*
|
|
* In other words, the value of a use of `that` may flow into the right-hand side of a
|
|
* definition of this node.
|
|
*/
|
|
pragma[inline]
|
|
predicate refersTo(Node that) { this.asSink() = that.getAValueReachableFromSource() }
|
|
|
|
/**
|
|
* Gets the data-flow node that gives rise to this node, if any.
|
|
*/
|
|
DataFlow::Node getInducingNode() {
|
|
this = Impl::MkClassInstance(result) or
|
|
this = Impl::MkUse(result) or
|
|
this = Impl::MkDef(result) or
|
|
this = Impl::MkSyntheticCallbackArg(_, _, result)
|
|
}
|
|
|
|
/**
|
|
* Gets the location of this API node, if it corresponds to a program element with a source location.
|
|
*/
|
|
final Location getLocation() { result = this.getInducingNode().getLocation() }
|
|
|
|
/**
|
|
* DEPRECATED: Use `getLocation().hasLocationInfo()` instead.
|
|
*
|
|
* Holds if this node is located in file `path` between line `startline`, column `startcol`,
|
|
* and line `endline`, column `endcol`.
|
|
*
|
|
* For nodes that do not have a meaningful location, `path` is the empty string and all other
|
|
* parameters are zero.
|
|
*/
|
|
deprecated predicate hasLocationInfo(
|
|
string path, int startline, int startcol, int endline, int endcol
|
|
) {
|
|
this.getLocation().hasLocationInfo(path, startline, startcol, endline, endcol)
|
|
or
|
|
not exists(this.getLocation()) and
|
|
path = "" and
|
|
startline = 0 and
|
|
startcol = 0 and
|
|
endline = 0 and
|
|
endcol = 0
|
|
}
|
|
|
|
/**
|
|
* Gets a textual representation of this node.
|
|
*/
|
|
string toString() {
|
|
none() // defined in subclasses
|
|
}
|
|
|
|
/**
|
|
* Gets a path of the given `length` from the root to this node.
|
|
*/
|
|
private string getAPath(int length) {
|
|
this instanceof Impl::MkRoot and
|
|
length = 0 and
|
|
result = ""
|
|
or
|
|
exists(Node pred, Label::ApiLabel lbl, string predpath |
|
|
Impl::edge(pred, lbl, this) and
|
|
predpath = pred.getAPath(length - 1) and
|
|
exists(string dot | if length = 1 then dot = "" else dot = "." |
|
|
result = predpath + dot + lbl and
|
|
// avoid producing strings longer than 1MB
|
|
result.length() < 1000 * 1000
|
|
)
|
|
) and
|
|
length in [1 .. Impl::distanceFromRoot(this)]
|
|
}
|
|
|
|
/** Gets the shortest distance from the root to this node in the API graph. */
|
|
int getDepth() { result = Impl::distanceFromRoot(this) }
|
|
}
|
|
|
|
/** The root node of an API graph. */
|
|
class Root extends Node, Impl::MkRoot {
|
|
override string toString() { result = "root" }
|
|
}
|
|
|
|
/** A node corresponding to a definition of an API component. */
|
|
class Definition extends Node, Impl::TDef {
|
|
override string toString() { result = "def " + this.getPath() }
|
|
}
|
|
|
|
/** A node corresponding to the use of an API component. */
|
|
class Use extends Node, Impl::TUse {
|
|
override string toString() { result = "use " + this.getPath() }
|
|
}
|
|
|
|
/** Gets the root node. */
|
|
Root root() { any() }
|
|
|
|
/** Gets a node corresponding to an import of module `m`. */
|
|
Node moduleImport(string m) {
|
|
result = Internal::getAModuleImportRaw(m)
|
|
or
|
|
result = ModelOutput::getATypeNode(m, "")
|
|
}
|
|
|
|
/** Gets a node corresponding to an export of module `m`. */
|
|
Node moduleExport(string m) { result = Impl::MkModuleDef(m).(Node).getMember("exports") }
|
|
|
|
/** Provides helper predicates for accessing API-graph nodes. */
|
|
module Node {
|
|
/** Gets a node whose type has the given qualified name. */
|
|
Node ofType(string moduleName, string exportedName) {
|
|
result = Internal::getANodeOfTypeRaw(moduleName, exportedName)
|
|
or
|
|
result = ModelOutput::getATypeNode(moduleName, exportedName)
|
|
}
|
|
}
|
|
|
|
/** Provides access to API graph nodes without taking into account types from models. */
|
|
module Internal {
|
|
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
|
|
Node getAModuleImportRaw(string m) {
|
|
result = Impl::MkModuleImport(m) or
|
|
result = Impl::MkModuleImport(m).(Node).getMember("default")
|
|
}
|
|
|
|
/** Gets a node whose type has the given qualified name, not including types from models. */
|
|
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
|
|
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
|
|
or
|
|
exportedName = "" and
|
|
result = getAModuleImportRaw(moduleName)
|
|
}
|
|
|
|
/** Gets a sink node that represents instances of `cls`. */
|
|
Node getClassInstance(DataFlow::ClassNode cls) { result = Impl::MkClassInstance(cls) }
|
|
}
|
|
|
|
/**
|
|
* An API entry point.
|
|
*
|
|
* By default, API graph nodes are only created for nodes that come from an external
|
|
* library or escape into an external library. The points where values are cross the boundary
|
|
* between codebases are called "entry points".
|
|
*
|
|
* Imports and exports are considered entry points by default, but additional entry points may
|
|
* be added by extending this class. Typical examples include global variables.
|
|
*/
|
|
abstract class EntryPoint extends string {
|
|
bindingset[this]
|
|
EntryPoint() { any() }
|
|
|
|
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
|
|
DataFlow::SourceNode getASource() { none() }
|
|
|
|
/** Gets a data-flow node where a value leaves the current codebase through this entry-point. */
|
|
DataFlow::Node getASink() { none() }
|
|
|
|
/** Gets an API-node for this entry point. */
|
|
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
|
}
|
|
|
|
/**
|
|
* A class for contributing new steps for tracking uses of an API.
|
|
*/
|
|
class AdditionalUseStep extends Unit {
|
|
/**
|
|
* Holds if use nodes should flow from `pred` to `succ`.
|
|
*/
|
|
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ) { none() }
|
|
}
|
|
|
|
private module AdditionalUseStep {
|
|
pragma[nomagic]
|
|
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ) {
|
|
any(AdditionalUseStep st).step(pred, succ)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides the actual implementation of API graphs, cached for performance.
|
|
*
|
|
* Ideally, we'd like nodes to correspond to (global) access paths, with edge labels
|
|
* corresponding to extending the access path by one element. We also want to be able to map
|
|
* nodes to their definitions and uses in the data-flow graph, and this should happen modulo
|
|
* (inter-procedural) data flow.
|
|
*
|
|
* This, however, is not easy to implement, since access paths can have unbounded length
|
|
* and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing
|
|
* a condition like "this node hasn't been involved in constructing any predecessor of
|
|
* this node in the API graph" without negative recursion is tricky.
|
|
*
|
|
* So instead most nodes are directly associated with a data-flow node, representing
|
|
* either a use or a definition of an API component. This ensures that we only have a finite
|
|
* number of nodes. However, we can now have multiple nodes with the same access
|
|
* path, which are essentially indistinguishable for a client of the API.
|
|
*
|
|
* On the other hand, a single node can have multiple access paths (which is, of
|
|
* course, unavoidable). We pick as canonical the alphabetically least access path with
|
|
* shortest length.
|
|
*/
|
|
cached
|
|
private module Impl {
|
|
cached
|
|
newtype TApiNode =
|
|
MkRoot() or
|
|
MkModuleDef(string m) { exists(MkModuleExport(m)) } or
|
|
MkModuleUse(string m) { exists(MkModuleImport(m)) } or
|
|
MkModuleExport(string m) {
|
|
exists(Module mod | mod = importableModule(m) |
|
|
// exclude modules that don't actually export anything
|
|
exports(m, _)
|
|
or
|
|
exports(m, _, _)
|
|
or
|
|
exists(NodeModule nm | nm = mod |
|
|
exists(Ssa::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()]))
|
|
)
|
|
)
|
|
} or
|
|
MkModuleImport(string m) {
|
|
imports(_, m)
|
|
or
|
|
any(TypeAnnotation n).hasQualifiedName(m, _)
|
|
or
|
|
any(Type t).hasUnderlyingType(m, _)
|
|
} or
|
|
MkClassInstance(DataFlow::ClassNode cls) { needsDefNode(cls) } or
|
|
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
|
|
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
|
|
/** A use of a TypeScript type. */
|
|
MkTypeUse(string moduleName, string exportName) {
|
|
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
|
|
or
|
|
any(Type t).hasUnderlyingType(moduleName, exportName)
|
|
} or
|
|
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
|
|
trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode())
|
|
}
|
|
|
|
private predicate needsDefNode(DataFlow::ClassNode cls) {
|
|
hasSemantics(cls) and
|
|
(
|
|
cls = trackDefNode(_)
|
|
or
|
|
cls.getAnInstanceReference() = trackDefNode(_)
|
|
or
|
|
needsDefNode(cls.getADirectSubClass())
|
|
)
|
|
}
|
|
|
|
class TDef = MkModuleDef or TNonModuleDef;
|
|
|
|
class TNonModuleDef = MkModuleExport or MkClassInstance or MkDef or MkSyntheticCallbackArg;
|
|
|
|
class TUse = MkModuleUse or MkModuleImport or MkUse or MkTypeUse;
|
|
|
|
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
|
|
|
|
/** Holds if `imp` is an import of module `m`. */
|
|
private predicate imports(DataFlow::Node imp, string m) {
|
|
imp = DataFlow::moduleImport(m) and
|
|
// path must not start with a dot or a slash
|
|
m.regexpMatch("[^./].*") and
|
|
hasSemantics(imp)
|
|
}
|
|
|
|
/**
|
|
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
|
|
* incoming edge from `base` labeled `lbl` in the API graph.
|
|
*/
|
|
cached
|
|
predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) {
|
|
hasSemantics(rhs) and
|
|
(
|
|
base = MkRoot() and
|
|
exists(EntryPoint e |
|
|
lbl = Label::entryPoint(e) and
|
|
rhs = e.getASink()
|
|
)
|
|
or
|
|
exists(string m, string prop |
|
|
base = MkModuleExport(m) and
|
|
lbl = Label::member(prop) and
|
|
exports(m, prop, rhs)
|
|
)
|
|
or
|
|
exists(DataFlow::Node def, DataFlow::SourceNode pred |
|
|
rhs(base, def) and pred = trackDefNode(def)
|
|
|
|
|
// from `x` to a definition of `x.prop`
|
|
exists(DataFlow::PropWrite pw | pw = pred.getAPropertyWrite() |
|
|
lbl = Label::memberFromRef(pw) and
|
|
rhs = pw.getRhs()
|
|
)
|
|
or
|
|
// special case: from `require('m')` to an export of `prop` in `m`
|
|
exists(Import imp, Module m, string prop |
|
|
pred = imp.getImportedModuleNode() and
|
|
m = imp.getImportedModule() and
|
|
lbl = Label::member(prop) and
|
|
rhs = m.getAnExportedValue(prop)
|
|
)
|
|
or
|
|
// In general, turn store steps into member steps for def-nodes
|
|
exists(string prop |
|
|
PreCallGraphStep::storeStep(rhs, pred, prop) and
|
|
lbl = Label::member(prop) and
|
|
not DataFlow::PseudoProperties::isPseudoProperty(prop)
|
|
)
|
|
or
|
|
exists(DataFlow::ContentSet contents |
|
|
SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and
|
|
lbl = Label::content(contents.getAStoreContent())
|
|
)
|
|
or
|
|
exists(DataFlow::FunctionNode fn |
|
|
fn = pred and
|
|
lbl = Label::return()
|
|
|
|
|
if fn.getFunction().isAsync() then rhs = fn.getReturnNode() else rhs = fn.getAReturn()
|
|
)
|
|
or
|
|
lbl = Label::promised() and
|
|
PromiseFlow::storeStep(rhs, pred, Promises::valueProp())
|
|
or
|
|
lbl = Label::promisedError() and
|
|
PromiseFlow::storeStep(rhs, pred, Promises::errorProp())
|
|
or
|
|
// The return-value of a getter G counts as a definition of property G
|
|
// (Ordinary methods and properties are handled as PropWrite nodes)
|
|
exists(string name | lbl = Label::member(name) |
|
|
rhs = pred.(DataFlow::ObjectLiteralNode).getPropertyGetter(name).getAReturn()
|
|
or
|
|
rhs =
|
|
pred.(DataFlow::ClassNode)
|
|
.getStaticMember(name, DataFlow::MemberKind::getter())
|
|
.getAReturn()
|
|
)
|
|
or
|
|
// Handle rest parameters escaping into external code. For example:
|
|
//
|
|
// function foo(...rest) {
|
|
// externalFunc(rest);
|
|
// }
|
|
//
|
|
// Here, 'rest' reaches a def-node at the call to externalFunc, so we need to ensure
|
|
// the arguments passed to 'foo' are stored in the 'rest' array.
|
|
exists(Function fun, DataFlow::InvokeNode invoke, int argIndex, Parameter rest |
|
|
fun.getRestParameter() = rest and
|
|
rest.flow() = pred and
|
|
invoke.getACallee() = fun and
|
|
invoke.getArgument(argIndex) = rhs and
|
|
argIndex >= rest.getIndex() and
|
|
lbl = Label::member((argIndex - rest.getIndex()).toString())
|
|
)
|
|
)
|
|
or
|
|
exists(DataFlow::ClassNode cls, string name |
|
|
base = MkClassInstance(cls) and
|
|
lbl = Label::member(name)
|
|
|
|
|
rhs = cls.getInstanceMethod(name)
|
|
or
|
|
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
|
|
)
|
|
or
|
|
exists(DataFlow::FunctionNode f |
|
|
f.getFunction().isAsync() and
|
|
base = MkDef(f.getReturnNode())
|
|
|
|
|
lbl = Label::promised() and
|
|
rhs = f.getAReturn()
|
|
or
|
|
lbl = Label::promisedError() and
|
|
rhs = f.getExceptionalReturn()
|
|
)
|
|
or
|
|
exists(int i | argumentPassing(base, i, rhs) |
|
|
lbl = Label::parameter(i)
|
|
or
|
|
i = -1 and lbl = Label::receiver()
|
|
)
|
|
or
|
|
exists(int i |
|
|
spreadArgumentPassing(base, i, rhs) and
|
|
lbl = Label::spreadArgument(i)
|
|
)
|
|
or
|
|
exists(DataFlow::SourceNode src, DataFlow::PropWrite pw |
|
|
use(base, src) and pw = trackUseNode(src).getAPropertyWrite() and rhs = pw.getRhs()
|
|
|
|
|
lbl = Label::memberFromRef(pw)
|
|
)
|
|
)
|
|
or
|
|
decoratorDualEdge(base, lbl, rhs)
|
|
or
|
|
decoratorRhsEdge(base, lbl, rhs)
|
|
or
|
|
exists(DataFlow::PropWrite write |
|
|
decoratorPropEdge(base, lbl, write) and
|
|
rhs = write.getRhs()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `arg` is passed as the `i`th argument to a use of `base`, either by means of a
|
|
* full invocation, or in a partial function application.
|
|
*
|
|
* The receiver is considered to be argument -1.
|
|
*/
|
|
private predicate argumentPassing(TApiNode base, int i, DataFlow::Node arg) {
|
|
exists(DataFlow::Node use, DataFlow::SourceNode pred, int bound |
|
|
use(base, use) and pred = trackUseNode(use, _, bound, "")
|
|
|
|
|
arg = pred.getAnInvocation().getArgument(i - bound)
|
|
or
|
|
arg = pred.getACall().getReceiver() and
|
|
bound = 0 and
|
|
i = -1
|
|
or
|
|
exists(DataFlow::PartialInvokeNode pin, DataFlow::Node callback | pred.flowsTo(callback) |
|
|
pin.isPartialArgument(callback, arg, i - bound)
|
|
or
|
|
arg = pin.getBoundReceiver(callback) and
|
|
bound = 0 and
|
|
i = -1
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private int firstSpreadIndex(InvokeExpr expr) {
|
|
result = min(int i | expr.getArgument(i) instanceof SpreadElement)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private InvokeExpr getAnInvocationWithSpread(DataFlow::SourceNode node, int i) {
|
|
result = node.getAnInvocation().asExpr() and
|
|
i = firstSpreadIndex(result)
|
|
}
|
|
|
|
private predicate spreadArgumentPassing(TApiNode base, int i, DataFlow::Node spreadArray) {
|
|
exists(
|
|
DataFlow::Node use, DataFlow::SourceNode pred, int bound, InvokeExpr invoke, int spreadPos
|
|
|
|
|
use(base, use) and
|
|
pred = trackUseNode(use, _, bound, "") and
|
|
invoke = getAnInvocationWithSpread(pred, spreadPos) and
|
|
spreadArray = invoke.getArgument(spreadPos).(SpreadElement).getOperand().flow() and
|
|
i = bound + spreadPos
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `rhs` is the right-hand side of a definition of node `nd`.
|
|
*/
|
|
cached
|
|
predicate rhs(TApiNode nd, DataFlow::Node rhs) {
|
|
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
|
|
or
|
|
nd = MkDef(rhs)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ref` is a read of a property described by `lbl` on `pred`, and
|
|
* `propDesc` is compatible with that property, meaning it is either the
|
|
* name of the property itself or the empty string.
|
|
*/
|
|
pragma[noinline]
|
|
private predicate propertyRead(
|
|
DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref
|
|
) {
|
|
ref = pred.getAPropertyRead() and
|
|
lbl = Label::memberFromRef(ref) and
|
|
(
|
|
lbl = Label::member(propDesc)
|
|
or
|
|
propDesc = ""
|
|
)
|
|
or
|
|
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and
|
|
lbl = Label::promised() and
|
|
(propDesc = Promises::valueProp() or propDesc = "")
|
|
or
|
|
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and
|
|
lbl = Label::promisedError() and
|
|
(propDesc = Promises::errorProp() or propDesc = "")
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::ClassNode getALocalSubclass(DataFlow::SourceNode node) {
|
|
result.getASuperClassNode().getALocalSource() = node
|
|
}
|
|
|
|
bindingset[node]
|
|
pragma[inline_late]
|
|
private DataFlow::ClassNode getALocalSubclassFwd(DataFlow::SourceNode node) {
|
|
result = getALocalSubclass(node)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
|
|
* `lbl` in the API graph.
|
|
*/
|
|
cached
|
|
predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
|
|
hasSemantics(ref) and
|
|
(
|
|
base = MkRoot() and
|
|
exists(EntryPoint e |
|
|
lbl = Label::entryPoint(e) and
|
|
ref = e.getASource()
|
|
)
|
|
or
|
|
// property reads
|
|
exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc |
|
|
use(base, src) and
|
|
pred = trackUseNode(src, false, 0, propDesc) and
|
|
propertyRead(pred, propDesc, lbl, ref) and
|
|
// `module.exports` is special: it is a use of a def-node, not a use-node,
|
|
// so we want to exclude it here
|
|
(base instanceof TNonModuleDef or base instanceof TUse)
|
|
)
|
|
or
|
|
exists(DataFlow::SourceNode src, DataFlow::SourceNode pred |
|
|
use(base, src) and pred = trackUseNode(src)
|
|
|
|
|
lbl = Label::instance() and
|
|
ref = pred.getAnInstantiation()
|
|
or
|
|
lbl = Label::return() and
|
|
ref = pred.getAnInvocation()
|
|
or
|
|
lbl = Label::forwardingFunction() and
|
|
DataFlow::functionForwardingStep(pred.getALocalUse(), ref)
|
|
or
|
|
exists(DataFlow::ClassNode cls |
|
|
lbl = Label::instance() and
|
|
cls = getALocalSubclassFwd(pred).getADirectSubClass*()
|
|
|
|
|
ref = cls.getAReceiverNode()
|
|
or
|
|
ref = cls.getAClassReference().getAnInstantiation()
|
|
)
|
|
or
|
|
exists(string prop |
|
|
PreCallGraphStep::loadStep(pred.getALocalUse(), ref, prop) and
|
|
lbl = Label::member(prop) and
|
|
// avoid generating member edges like "$arrayElement$"
|
|
not DataFlow::PseudoProperties::isPseudoProperty(prop)
|
|
)
|
|
or
|
|
exists(DataFlow::ContentSet contents |
|
|
SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and
|
|
lbl = Label::content(contents.getAStoreContent())
|
|
)
|
|
)
|
|
or
|
|
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
|
|
rhs(base, def) and fn = trackDefNode(def)
|
|
|
|
|
exists(int i |
|
|
lbl = Label::parameter(i) and
|
|
ref = fn.getParameter(i)
|
|
)
|
|
or
|
|
lbl = Label::receiver() and
|
|
ref = fn.getReceiver()
|
|
)
|
|
or
|
|
exists(DataFlow::Node def, DataFlow::ClassNode cls, int i |
|
|
rhs(base, def) and cls = trackDefNode(def)
|
|
|
|
|
lbl = Label::parameter(i) and
|
|
ref = cls.getConstructor().getParameter(i)
|
|
)
|
|
or
|
|
exists(string moduleName, string exportName |
|
|
base = MkTypeUse(moduleName, exportName) and
|
|
lbl = Label::instance() and
|
|
ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName)
|
|
)
|
|
or
|
|
exists(DataFlow::InvokeNode call |
|
|
base = MkSyntheticCallbackArg(_, _, call) and
|
|
lbl = Label::parameter(1) and
|
|
ref = awaited(call)
|
|
)
|
|
or
|
|
decoratorDualEdge(base, lbl, ref)
|
|
or
|
|
decoratorUseEdge(base, lbl, ref)
|
|
or
|
|
// for fields and accessors, mark the reads as use-nodes
|
|
decoratorPropEdge(base, lbl, ref.(DataFlow::PropRead))
|
|
)
|
|
}
|
|
|
|
/** Holds if `base` is a use-node that flows to the decorator expression of the given decorator. */
|
|
pragma[nomagic]
|
|
private predicate useNodeFlowsToDecorator(TApiNode base, Decorator decorator) {
|
|
exists(DataFlow::SourceNode decoratorSrc |
|
|
use(base, decoratorSrc) and
|
|
trackUseNode(decoratorSrc).flowsToExpr(decorator.getExpression())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ref` corresponds to both a use and def-node that should have an incoming edge from `base` labelled `lbl`.
|
|
*
|
|
* This happens because the decorated value escapes into the decorator function, and is then replaced
|
|
* by the function's return value. In the JS analysis we generally assume decorators return their input,
|
|
* but library models may want to find the return value.
|
|
*/
|
|
private predicate decoratorDualEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
|
|
exists(ClassDefinition cls |
|
|
useNodeFlowsToDecorator(base, cls.getADecorator()) and
|
|
lbl = Label::decoratedClass() and
|
|
ref = DataFlow::valueNode(cls)
|
|
)
|
|
or
|
|
exists(MethodDefinition method |
|
|
useNodeFlowsToDecorator(base, method.getADecorator()) and
|
|
not method instanceof AccessorMethodDefinition and
|
|
lbl = Label::decoratedMember() and
|
|
ref = DataFlow::valueNode(method.getBody())
|
|
)
|
|
}
|
|
|
|
/** Holds if `ref` is a use that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */
|
|
private predicate decoratorUseEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
|
|
exists(SetterMethodDefinition accessor |
|
|
useNodeFlowsToDecorator(base,
|
|
[accessor.getADecorator(), accessor.getCorrespondingGetter().getADecorator()]) and
|
|
lbl = Label::decoratedMember() and
|
|
ref = DataFlow::parameterNode(accessor.getBody().getParameter(0))
|
|
)
|
|
or
|
|
exists(Parameter param |
|
|
useNodeFlowsToDecorator(base, param.getADecorator()) and
|
|
lbl = Label::decoratedParameter() and
|
|
ref = DataFlow::parameterNode(param)
|
|
)
|
|
}
|
|
|
|
/** Holds if `rhs` is a def node that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */
|
|
private predicate decoratorRhsEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) {
|
|
exists(GetterMethodDefinition accessor |
|
|
useNodeFlowsToDecorator(base,
|
|
[accessor.getADecorator(), accessor.getCorrespondingSetter().getADecorator()]) and
|
|
lbl = Label::decoratedMember() and
|
|
rhs = DataFlow::valueNode(accessor.getBody().getAReturnedExpr())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ref` is a reference to a field/accessor that should have en incoming edge from base labelled `lbl`.
|
|
*
|
|
* Since fields do not have their own data-flow nodes, we generate a node for each read or write.
|
|
* For property writes, the right-hand side becomes a def-node and property reads become use-nodes.
|
|
*
|
|
* For accessors this predicate computes each use of the accessor.
|
|
* The return value inside the accessor is computed by the `decoratorRhsEdge` predicate.
|
|
*/
|
|
private predicate decoratorPropEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::PropRef ref) {
|
|
exists(MemberDefinition fieldLike, DataFlow::ClassNode cls |
|
|
fieldLike instanceof FieldDefinition
|
|
or
|
|
fieldLike instanceof AccessorMethodDefinition
|
|
|
|
|
useNodeFlowsToDecorator(base, fieldLike.getADecorator()) and
|
|
lbl = Label::decoratedMember() and
|
|
cls = fieldLike.getDeclaringClass().flow() and
|
|
(
|
|
fieldLike.isStatic() and
|
|
ref = cls.getAClassReference().getAPropertyReference(fieldLike.getName())
|
|
or
|
|
not fieldLike.isStatic() and
|
|
ref = cls.getAnInstanceReference().getAPropertyReference(fieldLike.getName())
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ref` is a use of node `nd`.
|
|
*/
|
|
cached
|
|
predicate use(TApiNode nd, DataFlow::Node ref) {
|
|
exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) |
|
|
ref = DataFlow::moduleVarNode(mod)
|
|
)
|
|
or
|
|
exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) |
|
|
ref = DataFlow::exportsVarNode(mod)
|
|
or
|
|
exists(DataFlow::Node base | use(MkModuleDef(m), base) |
|
|
ref = trackUseNode(base).getAPropertyRead("exports")
|
|
)
|
|
)
|
|
or
|
|
exists(string m |
|
|
nd = MkModuleImport(m) and
|
|
ref = DataFlow::moduleImport(m)
|
|
)
|
|
or
|
|
exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) |
|
|
ref = cls.getAReceiverNode()
|
|
or
|
|
ref = cls.(DataFlow::ClassNode::FunctionStyleClass).getAPrototypeReference()
|
|
)
|
|
or
|
|
nd = MkUse(ref)
|
|
}
|
|
|
|
private import semmle.javascript.dataflow.TypeTracking
|
|
|
|
/**
|
|
* Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows.
|
|
*
|
|
* The flow from `nd` to that node may be inter-procedural, and is further described by three
|
|
* flags:
|
|
*
|
|
* - `promisified`: if true `true`, the flow goes through a promisification;
|
|
* - `boundArgs`: for function values, tracks how many arguments have been bound throughout
|
|
* the flow. To ensure termination, we somewhat arbitrarily constrain the number of bound
|
|
* arguments to be at most ten.
|
|
* - `prop`: if non-empty, the flow is only guaranteed to preserve the value of this property,
|
|
* and not necessarily the entire object.
|
|
*/
|
|
private DataFlow::SourceNode trackUseNode(
|
|
DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop,
|
|
DataFlow::TypeTracker t
|
|
) {
|
|
t.start() and
|
|
use(_, nd) and
|
|
result = nd and
|
|
promisified = false and
|
|
boundArgs = 0 and
|
|
prop = ""
|
|
or
|
|
exists(Promisify::PromisifyCall promisify |
|
|
trackUseNode(nd, false, boundArgs, prop, t.continue()).flowsTo(promisify.getArgument(0)) and
|
|
promisified = true and
|
|
prop = "" and
|
|
result = promisify
|
|
)
|
|
or
|
|
exists(DataFlow::PartialInvokeNode pin, DataFlow::Node pred, int predBoundArgs |
|
|
trackUseNode(nd, promisified, predBoundArgs, prop, t.continue()).flowsTo(pred) and
|
|
prop = "" and
|
|
result = pin.getBoundFunction(pred, boundArgs - predBoundArgs) and
|
|
boundArgs in [0 .. 10]
|
|
)
|
|
or
|
|
exists(DataFlow::SourceNode mid |
|
|
mid = trackUseNode(nd, promisified, boundArgs, prop, t) and
|
|
AdditionalUseStep::step(pragma[only_bind_out](mid), result)
|
|
)
|
|
or
|
|
exists(DataFlow::Node pred, string preprop |
|
|
trackUseNode(nd, promisified, boundArgs, preprop, t.continue()).flowsTo(pred) and
|
|
promisified = false and
|
|
boundArgs = 0 and
|
|
SharedTypeTrackingStep::loadStoreStep(pred, result, prop)
|
|
|
|
|
prop = preprop
|
|
or
|
|
preprop = ""
|
|
)
|
|
or
|
|
t = useStep(nd, promisified, boundArgs, prop, result)
|
|
}
|
|
|
|
/**
|
|
* Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially
|
|
* inter-procedural steps to some intermediate node, and then from that intermediate node to
|
|
* `res` in one step. The entire flow is described by the resulting `TypeTracker`.
|
|
*
|
|
* This predicate exists solely to enforce a better join order in `trackUseNode` above.
|
|
*/
|
|
pragma[noopt]
|
|
private DataFlow::TypeTracker useStep(
|
|
DataFlow::Node nd, boolean promisified, int boundArgs, string prop, DataFlow::Node res
|
|
) {
|
|
exists(DataFlow::TypeTracker t, StepSummary summary, DataFlow::SourceNode prev |
|
|
prev = trackUseNode(nd, promisified, boundArgs, prop, t) and
|
|
StepSummary::step(prev, res, summary) and
|
|
result = t.append(summary)
|
|
)
|
|
}
|
|
|
|
private DataFlow::SourceNode trackUseNode(
|
|
DataFlow::SourceNode nd, boolean promisified, int boundArgs, string prop
|
|
) {
|
|
result = trackUseNode(nd, promisified, boundArgs, prop, DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* Gets a node that is inter-procedurally reachable from `nd`, which is a use of some node.
|
|
*/
|
|
cached
|
|
DataFlow::SourceNode trackUseNode(DataFlow::SourceNode nd) {
|
|
result = trackUseNode(nd, false, 0, "")
|
|
}
|
|
|
|
private DataFlow::SourceNode trackDefNode(DataFlow::Node nd, DataFlow::TypeBackTracker t) {
|
|
t.start() and
|
|
rhs(_, nd) and
|
|
result = nd.getALocalSource()
|
|
or
|
|
// additional backwards step from `require('m')` to `exports` or `module.exports` in m
|
|
exists(Import imp | imp.getImportedModuleNode() = trackDefNode(nd, t.continue()) |
|
|
result = DataFlow::exportsVarNode(imp.getImportedModule())
|
|
or
|
|
result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports")
|
|
)
|
|
or
|
|
exists(ObjectExpr obj |
|
|
obj = trackDefNode(nd, t.continue()).asExpr() and
|
|
result =
|
|
obj.getAProperty()
|
|
.(SpreadProperty)
|
|
.getInit()
|
|
.(SpreadElement)
|
|
.getOperand()
|
|
.flow()
|
|
.getALocalSource()
|
|
)
|
|
or
|
|
t = defStep(nd, result)
|
|
}
|
|
|
|
/**
|
|
* Holds if `nd`, which is a def of an API-graph node, can be reached in zero or more potentially
|
|
* inter-procedural steps from some intermediate node, and `prev` flows into that intermediate node
|
|
* in one step. The entire flow is described by the resulting `TypeTracker`.
|
|
*
|
|
* This predicate exists solely to enforce a better join order in `trackDefNode` above.
|
|
*/
|
|
pragma[noopt]
|
|
private DataFlow::TypeBackTracker defStep(DataFlow::Node nd, DataFlow::SourceNode prev) {
|
|
exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next |
|
|
next = trackDefNode(nd, t) and
|
|
StepSummary::step(prev, next, summary) and
|
|
result = t.prepend(summary)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a node that inter-procedurally flows into `nd`, which is a definition of some node.
|
|
*/
|
|
cached
|
|
DataFlow::SourceNode trackDefNode(DataFlow::Node nd) {
|
|
result = trackDefNode(nd, DataFlow::TypeBackTracker::end())
|
|
}
|
|
|
|
private DataFlow::SourceNode awaited(DataFlow::InvokeNode call, DataFlow::TypeTracker t) {
|
|
t.startInPromise() and
|
|
exists(MkSyntheticCallbackArg(_, _, call)) and
|
|
result = call
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = awaited(call, t2).track(t2, t))
|
|
}
|
|
|
|
/**
|
|
* Gets a node holding the resolved value of promise `call`.
|
|
*/
|
|
private DataFlow::Node awaited(DataFlow::InvokeNode call) {
|
|
result = awaited(call, DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
|
*/
|
|
cached
|
|
predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) {
|
|
Stages::ApiStage::ref() and
|
|
exists(string m |
|
|
pred = MkRoot() and
|
|
lbl = Label::moduleLabel(m)
|
|
|
|
|
succ = MkModuleDef(m)
|
|
or
|
|
succ = MkModuleUse(m)
|
|
)
|
|
or
|
|
exists(string m |
|
|
pred = MkModuleDef(m) and
|
|
lbl = Label::member("exports") and
|
|
succ = MkModuleExport(m)
|
|
or
|
|
pred = MkModuleUse(m) and
|
|
lbl = Label::member("exports") and
|
|
succ = MkModuleImport(m)
|
|
)
|
|
or
|
|
exists(DataFlow::SourceNode ref |
|
|
use(pred, lbl, ref) and
|
|
succ = MkUse(ref)
|
|
)
|
|
or
|
|
exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) |
|
|
succ = MkDef(rhs)
|
|
or
|
|
exists(DataFlow::ClassNode cls |
|
|
cls.getAnInstanceReference().flowsTo(rhs) and
|
|
succ = MkClassInstance(cls)
|
|
)
|
|
)
|
|
or
|
|
exists(DataFlow::Node def |
|
|
rhs(pred, def) and
|
|
lbl = Label::instance() and
|
|
succ = MkClassInstance(trackDefNode(def))
|
|
)
|
|
or
|
|
exists(string moduleName, string exportName |
|
|
pred = MkModuleImport(moduleName) and
|
|
lbl = Label::member(exportName) and
|
|
succ = MkTypeUse(moduleName, exportName)
|
|
)
|
|
or
|
|
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
|
|
f.getFunction().isAsync() and
|
|
pred = MkDef(nd) and
|
|
f = trackDefNode(nd) and
|
|
lbl = Label::return() and
|
|
succ = MkDef(f.getReturnNode())
|
|
)
|
|
or
|
|
exists(int bound, DataFlow::InvokeNode call |
|
|
lbl = Label::parameter(bound + call.getNumArgument()) and
|
|
call = getAPromisifiedInvocation(pred, bound, succ)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if there is an edge from `pred` to `succ` in the API graph.
|
|
*/
|
|
private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) }
|
|
|
|
/** Gets the shortest distance from the root to `nd` in the API graph. */
|
|
cached
|
|
int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result)
|
|
|
|
/**
|
|
* Gets a call to a promisified function represented by `callee` where
|
|
* `bound` arguments have been bound.
|
|
*/
|
|
cached
|
|
DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) {
|
|
exists(DataFlow::SourceNode src |
|
|
Impl::use(callee, src) and
|
|
succ = Impl::MkSyntheticCallbackArg(src, bound, result)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An `InvokeNode` that is connected to the API graph.
|
|
*
|
|
* Can be used to reason about calls to an external API in which the correlation between
|
|
* parameters and/or return values must be retained.
|
|
*
|
|
* The member predicates `getParameter`, `getReturn`, and `getInstance` mimic the corresponding
|
|
* predicates from `API::Node`. These are guaranteed to exist and be unique to this call.
|
|
*/
|
|
class InvokeNode extends DataFlow::InvokeNode {
|
|
API::Node callee;
|
|
|
|
InvokeNode() {
|
|
this = callee.getReturn().asSource() or
|
|
this = callee.getInstance().asSource() or
|
|
this = Impl::getAPromisifiedInvocation(callee, _, _)
|
|
}
|
|
|
|
/** Gets the API node for the `i`th parameter of this invocation. */
|
|
pragma[nomagic]
|
|
Node getParameter(int i) {
|
|
result = callee.getParameter(i) and
|
|
result = this.getAParameterCandidate(i)
|
|
}
|
|
|
|
/**
|
|
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
|
*/
|
|
pragma[noinline]
|
|
private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) }
|
|
|
|
/** Gets the API node for a parameter of this invocation. */
|
|
Node getAParameter() { result = this.getParameter(_) }
|
|
|
|
/** Gets the API node for the last parameter of this invocation. */
|
|
Node getLastParameter() { result = this.getParameter(this.getNumArgument() - 1) }
|
|
|
|
/** Gets the API node for the return value of this call. */
|
|
Node getReturn() {
|
|
result = callee.getReturn() and
|
|
result.asSource() = this
|
|
}
|
|
|
|
/** Gets the API node for the object constructed by this invocation. */
|
|
Node getInstance() {
|
|
result = callee.getInstance() and
|
|
result.asSource() = this
|
|
}
|
|
}
|
|
|
|
/** A call connected to the API graph. */
|
|
class CallNode extends InvokeNode, DataFlow::CallNode { }
|
|
|
|
/** A `new` call connected to the API graph. */
|
|
class NewNode extends InvokeNode, DataFlow::NewNode { }
|
|
|
|
/** Provides classes modeling the various edges (labels) in the API graph. */
|
|
module Label {
|
|
/** A label in the API-graph */
|
|
class ApiLabel extends TLabel {
|
|
/** Gets a string representation of this label. */
|
|
string toString() { result = "???" }
|
|
}
|
|
|
|
/** Gets the edge label for the module `m`. */
|
|
LabelModule moduleLabel(string m) { result.getMod() = m }
|
|
|
|
/** Gets the `member` edge label for member `m`. */
|
|
bindingset[m]
|
|
bindingset[result]
|
|
LabelMember member(string m) { result.getProperty() = m }
|
|
|
|
/** Gets the `content` edge label for content `c`. */
|
|
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }
|
|
|
|
/**
|
|
* Gets the edge label for an unknown member.
|
|
*
|
|
* Currently this is represented the same way as an unknown array element, but this may
|
|
* change in the future.
|
|
*/
|
|
ApiLabel unknownMember() { result = arrayElement() }
|
|
|
|
/**
|
|
* Gets the edge label for an unknown array element.
|
|
*/
|
|
LabelContent arrayElement() { result.getContent().isUnknownArrayElement() }
|
|
|
|
/**
|
|
* Gets a property name referred to by the given dynamic property access,
|
|
* allowing one property flow step in the process (to allow flow through imports).
|
|
*
|
|
* This is to support code patterns where the property name is actually constant,
|
|
* but the property name has been factored into a library.
|
|
*/
|
|
private string getAnIndirectPropName(DataFlow::PropRef ref) {
|
|
exists(DataFlow::Node pred |
|
|
FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and
|
|
result = pred.getStringValue()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets unique result of `getAnIndirectPropName` if there is one.
|
|
*/
|
|
private string getIndirectPropName(DataFlow::PropRef ref) {
|
|
result = unique(string s | s = getAnIndirectPropName(ref))
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate isEnumeratedPropName(DataFlow::Node node) {
|
|
node.getAPredecessor*() instanceof EnumeratedPropName
|
|
}
|
|
|
|
/** Gets the `member` edge label for the given property reference. */
|
|
ApiLabel memberFromRef(DataFlow::PropRef pr) {
|
|
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
|
|
result = member(pn) and
|
|
// only consider properties with alphanumeric(-ish) names, excluding special properties
|
|
// and properties whose names look like they are meant to be internal
|
|
pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*")
|
|
)
|
|
or
|
|
not exists(pr.getPropertyName()) and
|
|
not exists(getIndirectPropName(pr)) and
|
|
// Avoid assignments in an extend-like pattern
|
|
not isEnumeratedPropName(pr.getPropertyNameExpr().flow()) and
|
|
result = arrayElement()
|
|
}
|
|
|
|
/** Gets the `instance` edge label. */
|
|
LabelInstance instance() { any() }
|
|
|
|
/**
|
|
* Gets the `parameter` edge label for the `i`th parameter.
|
|
*
|
|
* The receiver is considered to be parameter -1.
|
|
*/
|
|
LabelParameter parameter(int i) { result.getIndex() = i }
|
|
|
|
/** Gets the edge label for the receiver. */
|
|
LabelReceiver receiver() { any() }
|
|
|
|
/** Gets the edge label for a spread argument passed at index `i`. */
|
|
LabelSpreadArgument spreadArgument(int i) { result.getIndex() = i }
|
|
|
|
/** Gets the `return` edge label. */
|
|
LabelReturn return() { any() }
|
|
|
|
/** Gets the label representing a function wrapper that forwards to an underlying function. */
|
|
LabelForwardingFunction forwardingFunction() { any() }
|
|
|
|
/** Gets the `promised` edge label connecting a promise to its contained value. */
|
|
LabelContent promised() { result.getContent() = ContentPrivate::MkPromiseValue() }
|
|
|
|
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
|
|
LabelContent promisedError() { result.getContent() = ContentPrivate::MkPromiseError() }
|
|
|
|
/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
|
|
LabelDecoratedClass decoratedClass() { any() }
|
|
|
|
/** Gets the label for an edge leading from a value `D` to any method, field, or accessor that has `D` as a decorator. */
|
|
LabelDecoratedMethod decoratedMember() { any() }
|
|
|
|
/** Gets the label for an edge leading from a value `D` to any parameter that has `D` as a decorator. */
|
|
LabelDecoratedParameter decoratedParameter() { any() }
|
|
|
|
/** Gets an entry-point label for the entry-point `e`. */
|
|
LabelEntryPoint entryPoint(API::EntryPoint e) { result.getEntryPoint() = e }
|
|
|
|
private import LabelImpl
|
|
|
|
private module LabelImpl {
|
|
newtype TLabel =
|
|
MkLabelModule(string mod) {
|
|
exists(Impl::MkModuleExport(mod)) or
|
|
exists(Impl::MkModuleImport(mod))
|
|
} or
|
|
MkLabelInstance() or
|
|
MkLabelContent(DataFlow::Content content) or
|
|
MkLabelMember(string name) {
|
|
name instanceof PropertyName
|
|
or
|
|
exists(Impl::MkTypeUse(_, name))
|
|
} or
|
|
MkLabelParameter(int i) {
|
|
i =
|
|
[0 .. max(int args |
|
|
args = any(InvokeExpr invk).getNumArgument() or
|
|
args = any(Function f).getNumParameter()
|
|
)] or
|
|
i = [0 .. 10]
|
|
} or
|
|
MkLabelReceiver() or
|
|
MkLabelReturn() or
|
|
MkLabelSpreadArgument(int index) { index = [0 .. 10] } or
|
|
MkLabelDecoratedClass() or
|
|
MkLabelDecoratedMember() or
|
|
MkLabelDecoratedParameter() or
|
|
MkLabelForwardingFunction() or
|
|
MkLabelEntryPoint(API::EntryPoint e)
|
|
|
|
/** A label for an entry-point. */
|
|
class LabelEntryPoint extends ApiLabel, MkLabelEntryPoint {
|
|
API::EntryPoint e;
|
|
|
|
LabelEntryPoint() { this = MkLabelEntryPoint(e) }
|
|
|
|
/** Gets the EntryPoint associated with this label. */
|
|
API::EntryPoint getEntryPoint() { result = e }
|
|
|
|
override string toString() { result = "entryPoint(\"" + e + "\")" }
|
|
}
|
|
|
|
/** A label that gets a promised value. */
|
|
deprecated class LabelPromised extends ApiLabel {
|
|
LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
|
|
}
|
|
|
|
/** A label that gets a rejected promise. */
|
|
deprecated class LabelPromisedError extends ApiLabel {
|
|
LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
|
|
}
|
|
|
|
/** A label that gets the return value of a function. */
|
|
class LabelReturn extends ApiLabel, MkLabelReturn {
|
|
override string toString() { result = "getReturn()" }
|
|
}
|
|
|
|
/** A label for a module. */
|
|
class LabelModule extends ApiLabel, MkLabelModule {
|
|
string mod;
|
|
|
|
LabelModule() { this = MkLabelModule(mod) }
|
|
|
|
/** Gets the module associated with this label. */
|
|
string getMod() { result = mod }
|
|
|
|
// moduleImport is not neccesarilly the predicate to use, but it's close enough for most cases.
|
|
override string toString() { result = "moduleImport(\"" + mod + "\")" }
|
|
}
|
|
|
|
/** A label that gets an instance from a `new` call. */
|
|
class LabelInstance extends ApiLabel, MkLabelInstance {
|
|
override string toString() { result = "getInstance()" }
|
|
}
|
|
|
|
/** A label for a content. */
|
|
class LabelContent extends ApiLabel, MkLabelContent {
|
|
private DataFlow::Content content;
|
|
|
|
LabelContent() {
|
|
this = MkLabelContent(content) and
|
|
// Property names are represented by LabelMember to ensure additional property
|
|
// names from PreCallGraph step are included, as well as those from MkTypeUse.
|
|
not content instanceof ContentPrivate::MkPropertyContent
|
|
}
|
|
|
|
/** Gets the content associated with this label. */
|
|
DataFlow::Content getContent() { result = content }
|
|
|
|
private string specialisedToString() {
|
|
content instanceof ContentPrivate::MkPromiseValue and result = "getPromised()"
|
|
or
|
|
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
|
|
or
|
|
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
|
|
}
|
|
|
|
override string toString() {
|
|
result = this.specialisedToString()
|
|
or
|
|
not exists(this.specialisedToString()) and
|
|
result = "getContent(" + content + ")"
|
|
}
|
|
}
|
|
|
|
/** A label for the member named `prop`. */
|
|
class LabelMember extends ApiLabel, MkLabelMember {
|
|
private string prop;
|
|
|
|
LabelMember() { this = MkLabelMember(prop) }
|
|
|
|
/** Gets the property associated with this label. */
|
|
string getProperty() { result = prop }
|
|
|
|
override string toString() { result = "getMember(\"" + prop + "\")" }
|
|
}
|
|
|
|
/** A label for a member with an unknown name. */
|
|
deprecated class LabelUnknownMember extends LabelContent {
|
|
LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
|
|
}
|
|
|
|
/** A label for parameter `i`. */
|
|
class LabelParameter extends ApiLabel, MkLabelParameter {
|
|
int i;
|
|
|
|
LabelParameter() { this = MkLabelParameter(i) }
|
|
|
|
override string toString() { result = "getParameter(" + i + ")" }
|
|
|
|
/** Gets the index of the parameter for this label. */
|
|
int getIndex() { result = i }
|
|
}
|
|
|
|
/** A label for the receiver of call, that is, the value passed as `this`. */
|
|
class LabelReceiver extends ApiLabel, MkLabelReceiver {
|
|
override string toString() { result = "getReceiver()" }
|
|
}
|
|
|
|
/** A label representing an array passed as a spread argument at a given index. */
|
|
class LabelSpreadArgument extends ApiLabel, MkLabelSpreadArgument {
|
|
private int index;
|
|
|
|
LabelSpreadArgument() { this = MkLabelSpreadArgument(index) }
|
|
|
|
/** Gets the argument index at which the spread argument appears. */
|
|
int getIndex() { result = index }
|
|
|
|
override string toString() {
|
|
// Note: This refers to the internal edge that has no corresponding method on API::Node
|
|
result = "getSpreadArgument(" + index + ")"
|
|
}
|
|
}
|
|
|
|
/** A label for a function that is a wrapper around another function. */
|
|
class LabelForwardingFunction extends ApiLabel, MkLabelForwardingFunction {
|
|
override string toString() { result = "getForwardingFunction()" }
|
|
}
|
|
|
|
/** A label for a class decorated by the current value. */
|
|
class LabelDecoratedClass extends ApiLabel, MkLabelDecoratedClass {
|
|
override string toString() { result = "getADecoratedClass()" }
|
|
}
|
|
|
|
/** A label for a method, field, or accessor decorated by the current value. */
|
|
class LabelDecoratedMethod extends ApiLabel, MkLabelDecoratedMember {
|
|
override string toString() { result = "decoratedMember()" }
|
|
}
|
|
|
|
/** A label for a parameter decorated by the current value. */
|
|
class LabelDecoratedParameter extends ApiLabel, MkLabelDecoratedParameter {
|
|
override string toString() { result = "decoratedParameter()" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Holds if module `m` exports `rhs`. */
|
|
private predicate exports(string m, DataFlow::Node rhs) {
|
|
exists(Module mod | mod = importableModule(m) |
|
|
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
|
|
or
|
|
not mod.(ES2015Module).hasBothNamedAndDefaultExports() and
|
|
exports(m, "default", rhs)
|
|
or
|
|
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
|
|
rhs = assgn.getExpression().flow()
|
|
)
|
|
or
|
|
rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow()
|
|
)
|
|
}
|
|
|
|
/** Holds if module `m` exports `rhs` under the name `prop`. */
|
|
private predicate exports(string m, string prop, DataFlow::Node rhs) {
|
|
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
|
|
not exp.isTypeOnly() and
|
|
rhs = exp.getSourceNode(prop)
|
|
or
|
|
exists(Variable v |
|
|
exp.exportsAs(v, prop) and
|
|
rhs = v.getAnAssignedExpr().flow()
|
|
)
|
|
)
|
|
}
|
|
|
|
/** Gets the definition of module `m`. */
|
|
private Module importableModule(string m) {
|
|
exists(NpmPackage pkg, PackageJson json | json = pkg.getPackageJson() and not json.isPrivate() |
|
|
result = pkg.getMainModule() and
|
|
not result.isExterns() and
|
|
m = pkg.getPackageName()
|
|
)
|
|
}
|