mirror of
https://github.com/github/codeql.git
synced 2026-04-25 16:55:19 +02:00
JS: Instantiate flow summary library
This commit is contained in:
92
javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll
Normal file
92
javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll
Normal file
@@ -0,0 +1,92 @@
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as Impl
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImplSpecific
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
|
||||
private import semmle.javascript.dataflow.internal.DataFlowPrivate
|
||||
|
||||
class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
module SummaryComponent {
|
||||
private import Impl::Public::SummaryComponent as SC
|
||||
|
||||
predicate parameter = SC::parameter/1;
|
||||
|
||||
predicate argument = SC::argument/1;
|
||||
|
||||
predicate content = SC::content/1;
|
||||
|
||||
predicate withoutContent = SC::withoutContent/1;
|
||||
|
||||
predicate withContent = SC::withContent/1;
|
||||
|
||||
class SyntheticGlobal = SC::SyntheticGlobal;
|
||||
|
||||
/** Gets a summary component that represents a receiver. */
|
||||
SummaryComponent receiver() { result = argument(MkThisParameter()) }
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
SummaryComponent return() { result = SC::return(MkNormalReturnKind()) }
|
||||
|
||||
/** Gets a summary component that represents the exception thrown from a call. */
|
||||
SummaryComponent exceptionalReturn() { result = SC::return(MkExceptionalReturnKind()) }
|
||||
}
|
||||
|
||||
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
|
||||
|
||||
/** Provides predicates for constructing stacks of summary components. */
|
||||
module SummaryComponentStack {
|
||||
private import Impl::Public::SummaryComponentStack as SCS
|
||||
|
||||
predicate singleton = SCS::singleton/1;
|
||||
|
||||
predicate push = SCS::push/2;
|
||||
|
||||
predicate argument = SCS::argument/1;
|
||||
|
||||
/** Gets a singleton stack representing a receiver. */
|
||||
SummaryComponentStack receiver() { result = singleton(SummaryComponent::receiver()) }
|
||||
|
||||
/** Gets a singleton stack representing the return value of a call. */
|
||||
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
|
||||
|
||||
/** Gets a singleton stack representing the exception thrown from a call. */
|
||||
SummaryComponentStack exceptionalReturn() {
|
||||
result = singleton(SummaryComponent::exceptionalReturn())
|
||||
}
|
||||
}
|
||||
|
||||
/** A callable with a flow summary, identified by a unique string. */
|
||||
abstract class SummarizedCallable extends LibraryCallable, Impl::Public::SummarizedCallable {
|
||||
bindingset[this]
|
||||
SummarizedCallable() { any() }
|
||||
|
||||
/**
|
||||
* Same as
|
||||
*
|
||||
* ```ql
|
||||
* propagatesFlow(
|
||||
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* but uses an external (string) representation of the input and output stacks.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
|
||||
|
||||
/**
|
||||
* Gets the synthesized parameter that results from an input specification
|
||||
* that starts with `Argument[s]` for this library callable.
|
||||
*/
|
||||
DataFlow::ParameterNode getParameter(string s) {
|
||||
exists(ParameterPosition pos |
|
||||
DataFlowImplCommon::parameterNode(result, MkLibraryCallable(this), pos) and
|
||||
s = getParameterPosition(pos)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
@@ -48,6 +49,7 @@ private module Cached {
|
||||
} or
|
||||
TConstructorThisArgumentNode(InvokeExpr e) { e instanceof NewExpr or e instanceof SuperCall } or
|
||||
TConstructorThisPostUpdate(Constructor ctor) or
|
||||
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
@@ -5,11 +5,24 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import semmle.javascript.dataflow.internal.Contents::Private
|
||||
private import semmle.javascript.dataflow.internal.VariableCapture
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
|
||||
private import sharedlib.FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
private class Node = DataFlow::Node;
|
||||
|
||||
class PostUpdateNode = DataFlow::PostUpdateNode;
|
||||
|
||||
class FlowSummaryNode extends DataFlow::Node, TFlowSummaryNode {
|
||||
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
|
||||
|
||||
/** Gets the summarized callable that this node belongs to. */
|
||||
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
|
||||
result = this.getSummaryNode().getSummarizedCallable()
|
||||
}
|
||||
|
||||
cached
|
||||
override string toString() { result = this.getSummaryNode().toString() }
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TReturnKind =
|
||||
MkNormalReturnKind() or
|
||||
@@ -33,6 +46,8 @@ private predicate returnNodeImpl(DataFlow::Node node, ReturnKind kind) {
|
||||
// See the models for AsyncAwait and Generator.
|
||||
not fun.isAsyncOrGenerator()
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::summaryReturnNode(node.(FlowSummaryNode).getSummaryNode(), kind)
|
||||
}
|
||||
|
||||
private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) {
|
||||
@@ -45,6 +60,8 @@ private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) {
|
||||
kind = MkExceptionalReturnKind() and result = call.asBoundCall(_).getExceptionalReturn()
|
||||
or
|
||||
kind = MkNormalReturnKind() and result = call.asAccessorCall().(DataFlow::PropRead)
|
||||
or
|
||||
FlowSummaryImpl::Private::summaryOutNode(call, result.(FlowSummaryNode).getSummaryNode(), kind)
|
||||
}
|
||||
|
||||
class ReturnNode extends DataFlow::Node {
|
||||
@@ -86,6 +103,9 @@ predicate postUpdatePair(Node pre, Node post) {
|
||||
pre = TThisNode(constructor) and
|
||||
post = TConstructorThisPostUpdate(constructor)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::summaryPostUpdateNode(post.(FlowSummaryNode).getSummaryNode(),
|
||||
pre.(FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
class CastNode extends DataFlow::Node instanceof EmptyType { }
|
||||
@@ -93,6 +113,7 @@ class CastNode extends DataFlow::Node instanceof EmptyType { }
|
||||
cached
|
||||
newtype TDataFlowCallable =
|
||||
MkSourceCallable(StmtContainer container) or
|
||||
MkLibraryCallable(LibraryCallable callable)
|
||||
|
||||
/**
|
||||
* A callable entity. This is a wrapper around either a `StmtContainer` or a `LibraryCallable`.
|
||||
@@ -122,6 +143,18 @@ class DataFlowCallable extends TDataFlowCallable {
|
||||
LibraryCallable asLibraryCallable() { this = MkLibraryCallable(result) }
|
||||
}
|
||||
|
||||
/** A callable defined in library code, identified by a unique string. */
|
||||
abstract class LibraryCallable extends string {
|
||||
bindingset[this]
|
||||
LibraryCallable() { any() }
|
||||
|
||||
/** Gets a call to this library callable. */
|
||||
DataFlow::InvokeNode getACall() { none() }
|
||||
|
||||
/** Same as `getACall()` except this does not depend on the call graph or API graph. */
|
||||
DataFlow::InvokeNode getACallSimple() { none() }
|
||||
}
|
||||
|
||||
private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosition pos) {
|
||||
p = c.asSourceCallable().(Function).getParameter(pos.asPositional()).flow()
|
||||
or
|
||||
@@ -130,6 +163,12 @@ private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosit
|
||||
pos.isFunctionSelfReference() and p = TFunctionSelfReferenceNode(c.asSourceCallable())
|
||||
or
|
||||
pos.isArgumentsArray() and p = TReflectiveParametersNode(c.asSourceCallable())
|
||||
or
|
||||
exists(FlowSummaryNode summaryNode |
|
||||
summaryNode = p and
|
||||
FlowSummaryImpl::Private::summaryParameterNode(summaryNode.getSummaryNode(), pos) and
|
||||
c.asLibraryCallable() = summaryNode.getSummarizedCallable()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
@@ -165,6 +204,8 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
|
||||
or
|
||||
// argument to setter (TODO: this has no post-update node)
|
||||
pos.asPositional() = 0 and n = call.asAccessorCall().(DataFlow::PropWrite).getRhs()
|
||||
or
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call, n.(FlowSummaryNode).getSummaryNode(), pos)
|
||||
}
|
||||
|
||||
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
|
||||
@@ -173,6 +214,10 @@ predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos
|
||||
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node node) {
|
||||
result.asSourceCallable() = node.getContainer()
|
||||
or
|
||||
result.asLibraryCallable() = node.(FlowSummaryNode).getSummarizedCallable()
|
||||
or
|
||||
result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable()
|
||||
}
|
||||
|
||||
private newtype TDataFlowType =
|
||||
@@ -189,6 +234,8 @@ DataFlowType getNodeType(Node node) { result = TTodoDataFlowType() and exists(no
|
||||
|
||||
predicate nodeIsHidden(Node node) {
|
||||
DataFlow::PathNode::shouldNodeBeHidden(node)
|
||||
or
|
||||
node instanceof FlowSummaryNode
|
||||
}
|
||||
|
||||
predicate neverSkipInPathGraph(Node node) {
|
||||
@@ -232,6 +279,11 @@ private newtype TDataFlowCall =
|
||||
node = TValueNode(any(PropAccess p)) or
|
||||
node = TPropNode(any(PropertyPattern p))
|
||||
} or
|
||||
MkSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
class DataFlowCall extends TDataFlowCall {
|
||||
DataFlowCallable getEnclosingCallable() { none() } // Overridden in subclass
|
||||
@@ -246,6 +298,13 @@ class DataFlowCall extends TDataFlowCall {
|
||||
|
||||
DataFlow::InvokeNode asBoundCall(int boundArgs) { this = MkBoundCall(result, boundArgs) }
|
||||
|
||||
|
||||
predicate isSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable enclosingCallable,
|
||||
FlowSummaryImpl::Private::SummaryNode receiver
|
||||
) {
|
||||
this = MkSummaryCall(enclosingCallable, receiver)
|
||||
}
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
@@ -334,6 +393,23 @@ private class AccessorCall extends DataFlowCall, MkAccessorCall {
|
||||
ref.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
class SummaryCall extends DataFlowCall, MkSummaryCall {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable enclosingCallable;
|
||||
private FlowSummaryImpl::Private::SummaryNode receiver;
|
||||
|
||||
SummaryCall() { this = MkSummaryCall(enclosingCallable, receiver) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
result.asLibraryCallable() = enclosingCallable
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "[summary] call to " + receiver + " in " + enclosingCallable
|
||||
}
|
||||
|
||||
/** Gets the receiver node. */
|
||||
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
|
||||
}
|
||||
|
||||
private int getMaxArity() {
|
||||
// TODO: account for flow summaries
|
||||
@@ -416,6 +492,11 @@ DataFlowCallable viableCallable(DataFlowCall node) {
|
||||
)
|
||||
or
|
||||
result.asSourceCallableNotExterns() = node.asAccessorCall().getAnAccessorCallee().getFunction()
|
||||
or
|
||||
exists(LibraryCallable callable |
|
||||
result = MkLibraryCallable(callable) and
|
||||
node.asOrdinaryCall() = [callable.getACall(), callable.getACallSimple()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,6 +536,9 @@ private predicate valuePreservingStep(Node node1, Node node2) {
|
||||
or
|
||||
node2 = FlowSteps::getThrowTarget(node1)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
|
||||
node2.(FlowSummaryNode).getSummaryNode(), true)
|
||||
or
|
||||
// Step from post-update nodes to local sources of the pre-update node. This emulates how JS usually tracks side effects.
|
||||
exists(PostUpdateNode postUpdate |
|
||||
node1 = postUpdate and
|
||||
@@ -480,6 +564,9 @@ predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediate
|
||||
predicate jumpStep(Node node1, Node node2) {
|
||||
valuePreservingStep(node1, node2) and
|
||||
node1.getContainer() != node2.getContainer()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(node1.(FlowSummaryNode).getSummaryNode(),
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,6 +584,13 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
not exists(read.getPropertyName()) and
|
||||
c = ContentSet::arrayElement()
|
||||
)
|
||||
or
|
||||
exists(ContentSet contentSet |
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
|
||||
contentSet, node2.(FlowSummaryNode).getSummaryNode())
|
||||
|
|
||||
c = contentSet
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the post-update node for which `node` is the corresponding pre-update node. */
|
||||
@@ -523,6 +617,10 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
// Target the post-update node if one exists (for object literals we do not generate post-update nodes)
|
||||
node2 = tryGetPostUpdate(write.getBase())
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode()) and
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,6 +629,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, ContentSet c) {
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -538,6 +637,7 @@ predicate clearsContent(Node n, ContentSet c) {
|
||||
* at node `n`.
|
||||
*/
|
||||
predicate expectsContent(Node n, ContentSet c) {
|
||||
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,6 +657,7 @@ int accessPathLimit() { result = 5 }
|
||||
* by default as a heuristic.
|
||||
*/
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) {
|
||||
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
@@ -568,6 +669,8 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
call.isSummaryCall(_, receiver.(FlowSummaryNode).getSummaryNode()) and exists(kind)
|
||||
or
|
||||
receiver = call.asOrdinaryCall().getCalleeNode() and exists(kind)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Provides JS specific classes and predicates for defining flow summaries.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.javascript.dataflow.internal.Contents::Private
|
||||
private import semmle.javascript.dataflow.FlowSummary as FlowSummary
|
||||
private import sharedlib.DataFlowImplCommon
|
||||
private import sharedlib.FlowSummaryImpl::Private as Private
|
||||
private import sharedlib.FlowSummaryImpl::Public
|
||||
import semmle.javascript.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax
|
||||
|
||||
private class Node = DataFlow::Node;
|
||||
|
||||
/**
|
||||
* A class of callables that are candidates for flow summary modeling.
|
||||
*/
|
||||
class SummarizedCallableBase = string;
|
||||
|
||||
/**
|
||||
* A class of callables that are candidates for neutral modeling.
|
||||
*/
|
||||
class NeutralCallableBase = string;
|
||||
|
||||
/**
|
||||
* Holds if a neutral model exists for `c` of kind `kind` and with provenance `provenance`.
|
||||
* Note: Neutral models have not been implemented for Javascript.
|
||||
*/
|
||||
predicate neutralElement(NeutralCallableBase c, string kind, string provenance) { none() }
|
||||
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
|
||||
/** Gets the parameter position representing a callback itself, if any. */
|
||||
ArgumentPosition callbackSelfParameterPosition() { result.isFunctionSelfReference() }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(Private::SummaryNode receiver) { receiver = result.getReceiver() }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(ContentSet c) { any() }
|
||||
|
||||
/** Gets the type of the parameter at the given position. */
|
||||
bindingset[c, pos]
|
||||
DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { any() }
|
||||
|
||||
/** Gets the return type of kind `rk` for callable `c`. */
|
||||
bindingset[c, rk]
|
||||
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
|
||||
|
||||
/**
|
||||
* Gets the type of the `i`th parameter in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
bindingset[t, pos]
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) { any() }
|
||||
|
||||
/**
|
||||
* Gets the return type of kind `rk` in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() }
|
||||
|
||||
/** Gets the type of synthetic global `sg`. */
|
||||
DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) { any() }
|
||||
|
||||
/**
|
||||
* Holds if an external flow summary exists for `c` with input specification
|
||||
* `input`, output specification `output`, kind `kind`, and provenance `provenance`.
|
||||
*/
|
||||
predicate summaryElement(
|
||||
FlowSummary::SummarizedCallable c, string input, string output, string kind, string provenance
|
||||
) {
|
||||
exists(boolean preservesValue |
|
||||
c.propagatesFlowExt(input, output, preservesValue) and
|
||||
(if preservesValue = true then kind = "value" else kind = "taint") and
|
||||
provenance = "manual"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a neutral summary model exists for `c` with provenance `provenance`,
|
||||
* which means that there is no flow through `c`.
|
||||
* Note. Neutral models have not been implemented for JS.
|
||||
*/
|
||||
predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
|
||||
|
||||
pragma[inline]
|
||||
private SummaryComponent makeContentComponents(
|
||||
Private::AccessPathToken token, string name, ContentSet contents
|
||||
) {
|
||||
token.getName() = name and
|
||||
result = FlowSummary::SummaryComponent::content(contents)
|
||||
or
|
||||
token.getName() = "With" + name and
|
||||
result = FlowSummary::SummaryComponent::withContent(contents)
|
||||
or
|
||||
token.getName() = "Without" + name and
|
||||
result = FlowSummary::SummaryComponent::withoutContent(contents)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
private SummaryComponent makePropertyContentComponents(
|
||||
Private::AccessPathToken token, string name, PropertyName content
|
||||
) {
|
||||
result = makeContentComponents(token, name, ContentSet::property(content))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content set corresponding to `Awaited[arg]`.
|
||||
*/
|
||||
private ContentSet getPromiseContent(string arg) {
|
||||
arg = "value" and result = ContentSet::promiseValue()
|
||||
or
|
||||
arg = "error" and result = ContentSet::promiseError()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate positionName(ParameterPosition pos, string operand) {
|
||||
operand = pos.asPositional().toString()
|
||||
or
|
||||
pos.isThis() and operand = "this"
|
||||
or
|
||||
pos.isFunctionSelfReference() and operand = "function"
|
||||
or
|
||||
pos.isArgumentsArray() and operand = "arguments-array"
|
||||
or
|
||||
operand = pos.asPositionalLowerBound() + ".."
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `operand` desugars to the given `pos`. Only used for parsing.
|
||||
*/
|
||||
bindingset[operand]
|
||||
private predicate desugaredPositionName(ParameterPosition pos, string operand) {
|
||||
operand = "any" and
|
||||
pos.asPositionalLowerBound() = 0
|
||||
or
|
||||
pos.asPositional() = AccessPathSyntax::AccessPath::parseInt(operand) // parse closed intervals
|
||||
}
|
||||
|
||||
bindingset[operand]
|
||||
private ParameterPosition parsePosition(string operand) {
|
||||
positionName(result, operand) or desugaredPositionName(result, operand)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary component for specification component `c`, if any.
|
||||
*
|
||||
* This covers all the JS-specific components of a flow summary.
|
||||
*/
|
||||
SummaryComponent interpretComponentSpecific(Private::AccessPathToken c) {
|
||||
c.getName() = "Argument" and
|
||||
result = FlowSummary::SummaryComponent::argument(parsePosition(c.getAnArgument()))
|
||||
or
|
||||
c.getName() = "Parameter" and
|
||||
result = FlowSummary::SummaryComponent::parameter(parsePosition(c.getAnArgument()))
|
||||
or
|
||||
result = makePropertyContentComponents(c, "Member", c.getAnArgument())
|
||||
or
|
||||
result = makeContentComponents(c, "Awaited", getPromiseContent(c.getAnArgument()))
|
||||
or
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "ArrayElement", ContentSet::arrayElement())
|
||||
or
|
||||
c.getAnArgument() = "?" and
|
||||
result = makeContentComponents(c, "ArrayElement", ContentSet::arrayElementUnknown())
|
||||
or
|
||||
exists(int n |
|
||||
n = c.getAnArgument().toInt() and
|
||||
result = makeContentComponents(c, "ArrayElement", ContentSet::arrayElementKnown(n))
|
||||
or
|
||||
// ArrayElement[n!] refers to index n, and never the unknown content
|
||||
c.getAnArgument().regexpCapture("(\\d+)!", 1).toInt() = n and
|
||||
result = makePropertyContentComponents(c, "ArrayElement", n.toString())
|
||||
or
|
||||
// ArrayElement[n..] refers to index n or greater
|
||||
n = AccessPathSyntax::AccessPath::parseLowerBound(c.getAnArgument()) and
|
||||
result = makeContentComponents(c, "ArrayElement", ContentSet::arrayElementLowerBoundFromInt(n))
|
||||
)
|
||||
or
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "SetElement", ContentSet::setElement())
|
||||
or
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "IteratorElement", ContentSet::iteratorElement())
|
||||
or
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "IteratorError", ContentSet::iteratorError())
|
||||
or
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "MapKey", ContentSet::mapKey())
|
||||
or
|
||||
//
|
||||
// Note: although it is supported internally, we currently do not expose a syntax for MapValue with a known key
|
||||
//
|
||||
c.getNumArgument() = 0 and
|
||||
result = makeContentComponents(c, "MapValue", ContentSet::mapValueAll())
|
||||
or
|
||||
c.getName() = "ReturnValue" and
|
||||
c.getAnArgument() = "exception" and
|
||||
result = SummaryComponent::return(MkExceptionalReturnKind())
|
||||
}
|
||||
|
||||
private string getMadStringFromContentSetAux(ContentSet cs) {
|
||||
cs = ContentSet::arrayElement() and
|
||||
result = "ArrayElement"
|
||||
or
|
||||
cs = ContentSet::arrayElementUnknown() and
|
||||
result = "ArrayElement[?]"
|
||||
or
|
||||
exists(int n |
|
||||
cs = ContentSet::arrayElementLowerBound(n) and
|
||||
result = "ArrayElement[" + n + "..]" and
|
||||
n > 0 // n=0 is just 'ArrayElement'
|
||||
or
|
||||
cs = ContentSet::arrayElementKnown(n) and
|
||||
result = "ArrayElement[" + n + "]"
|
||||
or
|
||||
n = cs.asPropertyName().toInt() and
|
||||
n >= 0 and
|
||||
result = "ArrayElement[" + n + "!]"
|
||||
)
|
||||
or
|
||||
cs = ContentSet::mapValueAll() and result = "MapValue"
|
||||
or
|
||||
cs = ContentSet::mapKey() and result = "MapKey"
|
||||
or
|
||||
cs = ContentSet::setElement() and result = "SetElement"
|
||||
or
|
||||
cs = ContentSet::iteratorElement() and result = "IteratorElement"
|
||||
or
|
||||
cs = ContentSet::iteratorError() and result = "IteratorError"
|
||||
or
|
||||
exists(string awaitedArg |
|
||||
cs = getPromiseContent(awaitedArg) and
|
||||
result = "Awaited[" + awaitedArg + "]"
|
||||
)
|
||||
}
|
||||
|
||||
private string getMadStringFromContentSet(ContentSet cs) {
|
||||
result = getMadStringFromContentSetAux(cs)
|
||||
or
|
||||
not exists(getMadStringFromContentSetAux(cs)) and
|
||||
result = "Member[" + cs.asSingleton() + "]"
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for MaD models. */
|
||||
string getMadRepresentationSpecific(SummaryComponent sc) {
|
||||
exists(ContentSet cs |
|
||||
sc = Private::TContentSummaryComponent(cs) and result = getMadStringFromContentSet(cs)
|
||||
)
|
||||
or
|
||||
exists(ReturnKind rk |
|
||||
sc = Private::TReturnSummaryComponent(rk) and
|
||||
not rk = getReturnValueKind() and
|
||||
result = "ReturnValue[" + rk + "]"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
|
||||
bindingset[pos]
|
||||
string getParameterPosition(ParameterPosition pos) { positionName(pos, result) and result != "any" }
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
bindingset[pos]
|
||||
string getArgumentPosition(ArgumentPosition pos) { positionName(pos, result) and result != "any" }
|
||||
|
||||
/** Holds if input specification component `c` needs a reference. */
|
||||
predicate inputNeedsReferenceSpecific(string c) { none() }
|
||||
|
||||
/** Holds if output specification component `c` needs a reference. */
|
||||
predicate outputNeedsReferenceSpecific(string c) { none() }
|
||||
|
||||
/** Gets the return kind corresponding to specification `"ReturnValue"`. */
|
||||
MkNormalReturnKind getReturnValueKind() { any() }
|
||||
|
||||
/**
|
||||
* All definitions in this module are required by the shared implementation
|
||||
* (for source/sink interpretation), but they are unused for JS, where
|
||||
* we rely on API graphs instead.
|
||||
*/
|
||||
private module UnusedSourceSinkInterpretation {
|
||||
/**
|
||||
* Holds if an external source specification exists for `n` with output specification
|
||||
* `output`, kind `kind`, and provenance `provenance`.
|
||||
*/
|
||||
predicate sourceElement(AstNode n, string output, string kind, string provenance) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an external sink specification exists for `n` with input specification
|
||||
* `input`, kind `kind` and provenance `provenance`.
|
||||
*/
|
||||
predicate sinkElement(AstNode n, string input, string kind, string provenance) { none() }
|
||||
|
||||
class SourceOrSinkElement = AstNode;
|
||||
|
||||
/** An entity used to interpret a source/sink specification. */
|
||||
class InterpretNode extends AstNode {
|
||||
/** Gets the element that this node corresponds to, if any. */
|
||||
SourceOrSinkElement asElement() { none() }
|
||||
|
||||
/** Gets the data-flow node that this node corresponds to, if any. */
|
||||
Node asNode() { none() }
|
||||
|
||||
/** Gets the call that this node corresponds to, if any. */
|
||||
DataFlowCall asCall() { none() }
|
||||
|
||||
/** Gets the callable that this node corresponds to, if any. */
|
||||
DataFlowCallable asCallable() { none() }
|
||||
|
||||
/** Gets the target of this call, if any. */
|
||||
StmtContainer getCallTarget() { none() }
|
||||
}
|
||||
|
||||
/** Provides additional sink specification logic. */
|
||||
predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
|
||||
|
||||
/** Provides additional source specification logic. */
|
||||
predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
|
||||
}
|
||||
|
||||
import UnusedSourceSinkInterpretation
|
||||
|
||||
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */
|
||||
bindingset[s]
|
||||
ArgumentPosition parseParamBody(string s) {
|
||||
s = "this" and result.isThis()
|
||||
or
|
||||
s = "function" and result.isFunctionSelfReference()
|
||||
or
|
||||
result.asPositional() = AccessPathSyntax::AccessPath::parseInt(s)
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
bindingset[s]
|
||||
ParameterPosition parseArgBody(string s) {
|
||||
result = parseParamBody(s) // Currently these are identical
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.javascript.dataflow.internal.Contents::Public
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
cached
|
||||
predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
|
||||
node2.(FlowSummaryNode).getSummaryNode(), false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
private import javascript
|
||||
|
||||
// This file provides the input to FlowSummaryImpl.qll, which is shared via identical-files.json.
|
||||
module Private {
|
||||
import semmle.javascript.dataflow.internal.DataFlowPrivate
|
||||
}
|
||||
|
||||
module Public {
|
||||
import semmle.javascript.dataflow.internal.Contents::Public
|
||||
|
||||
class Node = DataFlow::Node;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import semmle.javascript.dataflow.internal.FlowSummaryPrivate
|
||||
Reference in New Issue
Block a user