mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +01:00
Merge branch 'main' into kaspersv/prevent-python-join-order-regression
This commit is contained in:
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ private import semmle.code.csharp.dataflow.TaintTracking2
|
|||||||
*/
|
*/
|
||||||
predicate xssFlow(XssNode source, XssNode sink, string message) {
|
predicate xssFlow(XssNode source, XssNode sink, string message) {
|
||||||
// standard taint-tracking
|
// standard taint-tracking
|
||||||
exists(
|
exists(XssTracking::PathNode sourceNode, XssTracking::PathNode sinkNode |
|
||||||
TaintTrackingConfiguration c, DataFlow2::PathNode sourceNode, DataFlow2::PathNode sinkNode
|
|
||||||
|
|
|
||||||
sourceNode = source.asDataFlowNode() and
|
sourceNode = source.asDataFlowNode() and
|
||||||
sinkNode = sink.asDataFlowNode() and
|
sinkNode = sink.asDataFlowNode() and
|
||||||
c.hasFlowPath(sourceNode, sinkNode) and
|
XssTracking::flowPath(sourceNode, sinkNode) and
|
||||||
message =
|
message =
|
||||||
"is written to HTML or JavaScript" +
|
"is written to HTML or JavaScript" +
|
||||||
any(string explanation |
|
any(string explanation |
|
||||||
@@ -45,7 +43,7 @@ predicate xssFlow(XssNode source, XssNode sink, string message) {
|
|||||||
module PathGraph {
|
module PathGraph {
|
||||||
/** Holds if `(pred,succ)` is an edge in the graph of data flow path explanations. */
|
/** Holds if `(pred,succ)` is an edge in the graph of data flow path explanations. */
|
||||||
query predicate edges(XssNode pred, XssNode succ) {
|
query predicate edges(XssNode pred, XssNode succ) {
|
||||||
exists(DataFlow2::PathNode a, DataFlow2::PathNode b | DataFlow2::PathGraph::edges(a, b) |
|
exists(XssTracking::PathNode a, XssTracking::PathNode b | XssTracking::PathGraph::edges(a, b) |
|
||||||
pred.asDataFlowNode() = a and
|
pred.asDataFlowNode() = a and
|
||||||
succ.asDataFlowNode() = b
|
succ.asDataFlowNode() = b
|
||||||
)
|
)
|
||||||
@@ -56,7 +54,7 @@ module PathGraph {
|
|||||||
|
|
||||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||||
query predicate nodes(XssNode n, string key, string val) {
|
query predicate nodes(XssNode n, string key, string val) {
|
||||||
DataFlow2::PathGraph::nodes(n.asDataFlowNode(), key, val)
|
XssTracking::PathGraph::nodes(n.asDataFlowNode(), key, val)
|
||||||
or
|
or
|
||||||
xssFlow(n, n, _) and
|
xssFlow(n, n, _) and
|
||||||
key = "semmle.label" and
|
key = "semmle.label" and
|
||||||
@@ -69,13 +67,13 @@ module PathGraph {
|
|||||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||||
*/
|
*/
|
||||||
query predicate subpaths(XssNode arg, XssNode par, XssNode ret, XssNode out) {
|
query predicate subpaths(XssNode arg, XssNode par, XssNode ret, XssNode out) {
|
||||||
DataFlow2::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(), ret.asDataFlowNode(),
|
XssTracking::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(),
|
||||||
out.asDataFlowNode())
|
ret.asDataFlowNode(), out.asDataFlowNode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private newtype TXssNode =
|
private newtype TXssNode =
|
||||||
TXssDataFlowNode(DataFlow2::PathNode node) or
|
TXssDataFlowNode(XssTracking::PathNode node) or
|
||||||
TXssAspNode(AspInlineMember m)
|
TXssAspNode(AspInlineMember m)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,21 +88,25 @@ class XssNode extends TXssNode {
|
|||||||
/** Gets the location of this node. */
|
/** Gets the location of this node. */
|
||||||
Location getLocation() { none() }
|
Location getLocation() { none() }
|
||||||
|
|
||||||
/** Gets the data flow node corresponding to this node, if any. */
|
/**
|
||||||
DataFlow2::PathNode asDataFlowNode() { result = this.(XssDataFlowNode).getDataFlowNode() }
|
* Gets the data flow node corresponding to this node, if any.
|
||||||
|
*/
|
||||||
|
XssTracking::PathNode asDataFlowNode() { result = this.(XssDataFlowNode).getDataFlowNode() }
|
||||||
|
|
||||||
/** Gets the ASP inline code element corresponding to this node, if any. */
|
/** Gets the ASP inline code element corresponding to this node, if any. */
|
||||||
AspInlineMember asAspInlineMember() { result = this.(XssAspNode).getAspInlineMember() }
|
AspInlineMember asAspInlineMember() { result = this.(XssAspNode).getAspInlineMember() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A data flow node, viewed as an XSS flow node. */
|
/**
|
||||||
|
* A data flow node, viewed as an XSS flow node.
|
||||||
|
*/
|
||||||
class XssDataFlowNode extends TXssDataFlowNode, XssNode {
|
class XssDataFlowNode extends TXssDataFlowNode, XssNode {
|
||||||
DataFlow2::PathNode node;
|
XssTracking::PathNode node;
|
||||||
|
|
||||||
XssDataFlowNode() { this = TXssDataFlowNode(node) }
|
XssDataFlowNode() { this = TXssDataFlowNode(node) }
|
||||||
|
|
||||||
/** Gets the data flow node corresponding to this node. */
|
/** Gets the data flow node corresponding to this node. */
|
||||||
DataFlow2::PathNode getDataFlowNode() { result = node }
|
XssTracking::PathNode getDataFlowNode() { result = node }
|
||||||
|
|
||||||
override string toString() { result = node.toString() }
|
override string toString() { result = node.toString() }
|
||||||
|
|
||||||
@@ -136,9 +138,11 @@ abstract class Source extends DataFlow::Node { }
|
|||||||
abstract class Sanitizer extends DataFlow::ExprNode { }
|
abstract class Sanitizer extends DataFlow::ExprNode { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED: Use `XssTracking` instead.
|
||||||
|
*
|
||||||
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
|
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
|
||||||
*/
|
*/
|
||||||
class TaintTrackingConfiguration extends TaintTracking2::Configuration {
|
deprecated class TaintTrackingConfiguration extends TaintTracking2::Configuration {
|
||||||
TaintTrackingConfiguration() { this = "XSSDataFlowConfiguration" }
|
TaintTrackingConfiguration() { this = "XSSDataFlowConfiguration" }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
@@ -148,6 +152,29 @@ class TaintTrackingConfiguration extends TaintTracking2::Configuration {
|
|||||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
|
||||||
|
*/
|
||||||
|
module XssTrackingConfig implements DataFlow::ConfigSig {
|
||||||
|
/**
|
||||||
|
* Holds if `source` is a relevant data flow source.
|
||||||
|
*/
|
||||||
|
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `sink` is a relevant data flow sink.
|
||||||
|
*/
|
||||||
|
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if data flow through `node` is prohibited. This completely removes
|
||||||
|
* `node` from the data flow graph.
|
||||||
|
*/
|
||||||
|
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||||
|
}
|
||||||
|
|
||||||
|
module XssTracking = TaintTracking::Global<XssTrackingConfig>;
|
||||||
|
|
||||||
/** A source of remote user input. */
|
/** A source of remote user input. */
|
||||||
private class RemoteSource extends Source instanceof RemoteFlowSource { }
|
private class RemoteSource extends Source instanceof RemoteFlowSource { }
|
||||||
|
|
||||||
|
|||||||
@@ -12,38 +12,36 @@
|
|||||||
|
|
||||||
import csharp
|
import csharp
|
||||||
import semmle.code.csharp.frameworks.Format
|
import semmle.code.csharp.frameworks.Format
|
||||||
import DataFlow::PathGraph
|
import FormatInvalid::PathGraph
|
||||||
|
|
||||||
private class FormatConfiguration extends DataFlow::Configuration {
|
module FormatInvalidConfig implements DataFlow::ConfigSig {
|
||||||
FormatConfiguration() { this = "format" }
|
predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
|
predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) }
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node n) {
|
|
||||||
exists(FormatCall c | n.asExpr() = c.getFormatExpr())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module FormatInvalid = DataFlow::Global<FormatInvalidConfig>;
|
||||||
|
|
||||||
private predicate invalidFormatString(
|
private predicate invalidFormatString(
|
||||||
InvalidFormatString src, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
|
InvalidFormatString src, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
|
||||||
FormatCall call, string callString
|
FormatCall call, string callString
|
||||||
) {
|
) {
|
||||||
source.getNode().asExpr() = src and
|
source.getNode().asExpr() = src and
|
||||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
FormatInvalid::flowPath(source, sink) and
|
||||||
call.hasInsertions() and
|
call.hasInsertions() and
|
||||||
msg = "Invalid format string used in $@ formatting call." and
|
msg = "Invalid format string used in $@ formatting call." and
|
||||||
callString = "this"
|
callString = "this"
|
||||||
}
|
}
|
||||||
|
|
||||||
private predicate unusedArgument(
|
private predicate unusedArgument(
|
||||||
FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
|
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
|
||||||
ValidFormatString src, string srcString, Expr unusedExpr, string unusedString
|
ValidFormatString src, string srcString, Expr unusedExpr, string unusedString
|
||||||
) {
|
) {
|
||||||
exists(int unused |
|
exists(int unused |
|
||||||
source.getNode().asExpr() = src and
|
source.getNode().asExpr() = src and
|
||||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
FormatInvalid::flowPath(source, sink) and
|
||||||
unused = call.getASuppliedArgument() and
|
unused = call.getASuppliedArgument() and
|
||||||
not unused = src.getAnInsert() and
|
not unused = src.getAnInsert() and
|
||||||
not src.getValue() = "" and
|
not src.getValue() = "" and
|
||||||
@@ -55,13 +53,13 @@ private predicate unusedArgument(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private predicate missingArgument(
|
private predicate missingArgument(
|
||||||
FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
|
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
|
||||||
ValidFormatString src, string srcString
|
ValidFormatString src, string srcString
|
||||||
) {
|
) {
|
||||||
exists(int used, int supplied |
|
exists(int used, int supplied |
|
||||||
source.getNode().asExpr() = src and
|
source.getNode().asExpr() = src and
|
||||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
FormatInvalid::flowPath(source, sink) and
|
||||||
used = src.getAnInsert() and
|
used = src.getAnInsert() and
|
||||||
supplied = call.getSuppliedArguments() and
|
supplied = call.getSuppliedArguments() and
|
||||||
used >= supplied and
|
used >= supplied and
|
||||||
@@ -71,8 +69,8 @@ private predicate missingArgument(
|
|||||||
}
|
}
|
||||||
|
|
||||||
from
|
from
|
||||||
Element alert, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, Element extra1,
|
Element alert, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
|
||||||
string extra1String, Element extra2, string extra2String
|
Element extra1, string extra1String, Element extra2, string extra2String
|
||||||
where
|
where
|
||||||
invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
|
invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
|
||||||
extra2 = extra1 and
|
extra2 = extra1 and
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ private class ReturnNode extends DataFlow::ExprNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Conf extends DataFlow::Configuration {
|
module DisposeCallOnLocalIDisposableConfig implements DataFlow::ConfigSig {
|
||||||
Conf() { this = "NoDisposeCallOnLocalIDisposable" }
|
predicate isSource(DataFlow::Node node) {
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node node) {
|
|
||||||
node.asExpr() =
|
node.asExpr() =
|
||||||
any(LocalScopeDisposableCreation disposable |
|
any(LocalScopeDisposableCreation disposable |
|
||||||
// Only care about library types - user types often have spurious IDisposable declarations
|
// Only care about library types - user types often have spurious IDisposable declarations
|
||||||
@@ -37,7 +35,7 @@ private class Conf extends DataFlow::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node node) {
|
predicate isSink(DataFlow::Node node) {
|
||||||
// Things that return may be disposed elsewhere
|
// Things that return may be disposed elsewhere
|
||||||
node instanceof ReturnNode
|
node instanceof ReturnNode
|
||||||
or
|
or
|
||||||
@@ -80,23 +78,27 @@ private class Conf extends DataFlow::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
node2.asExpr() =
|
node2.asExpr() =
|
||||||
any(LocalScopeDisposableCreation other | other.getAnArgument() = node1.asExpr())
|
any(LocalScopeDisposableCreation other | other.getAnArgument() = node1.asExpr())
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isBarrierOut(DataFlow::Node node) {
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
this.isSink(node) and
|
isSink(node) and
|
||||||
not node instanceof ReturnNode
|
not node instanceof ReturnNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module DisposeCallOnLocalIDisposable = DataFlow::Global<DisposeCallOnLocalIDisposableConfig>;
|
||||||
|
|
||||||
/** Holds if `disposable` may not be disposed. */
|
/** Holds if `disposable` may not be disposed. */
|
||||||
predicate mayNotBeDisposed(LocalScopeDisposableCreation disposable) {
|
predicate mayNotBeDisposed(LocalScopeDisposableCreation disposable) {
|
||||||
exists(Conf conf, DataFlow::ExprNode e |
|
exists(DataFlow::ExprNode e |
|
||||||
e.getExpr() = disposable and
|
e.getExpr() = disposable and
|
||||||
conf.isSource(e) and
|
DisposeCallOnLocalIDisposableConfig::isSource(e) and
|
||||||
not exists(DataFlow::Node sink | conf.hasFlow(DataFlow::exprNode(disposable), sink) |
|
not exists(DataFlow::Node sink |
|
||||||
|
DisposeCallOnLocalIDisposable::flow(DataFlow::exprNode(disposable), sink)
|
||||||
|
|
|
||||||
sink instanceof ReturnNode
|
sink instanceof ReturnNode
|
||||||
implies
|
implies
|
||||||
sink.getEnclosingCallable() = disposable.getEnclosingCallable()
|
sink.getEnclosingCallable() = disposable.getEnclosingCallable()
|
||||||
|
|||||||
@@ -16,18 +16,21 @@ import csharp
|
|||||||
import semmle.code.csharp.security.dataflow.flowsources.Stored
|
import semmle.code.csharp.security.dataflow.flowsources.Stored
|
||||||
import semmle.code.csharp.security.dataflow.XSSQuery
|
import semmle.code.csharp.security.dataflow.XSSQuery
|
||||||
import semmle.code.csharp.security.dataflow.XSSSinks
|
import semmle.code.csharp.security.dataflow.XSSSinks
|
||||||
import semmle.code.csharp.dataflow.DataFlow2
|
import StoredXss::PathGraph
|
||||||
import DataFlow2::PathGraph
|
|
||||||
|
|
||||||
class StoredTaintTrackingConfiguration extends TaintTrackingConfiguration {
|
module StoredXssTrackingConfig implements DataFlow::ConfigSig {
|
||||||
override predicate isSource(DataFlow2::Node source) { source instanceof StoredFlowSource }
|
predicate isSource(DataFlow::Node source) { source instanceof StoredFlowSource }
|
||||||
|
|
||||||
|
predicate isSink = XssTrackingConfig::isSink/1;
|
||||||
|
|
||||||
|
predicate isBarrier = XssTrackingConfig::isBarrier/1;
|
||||||
}
|
}
|
||||||
|
|
||||||
from
|
module StoredXss = TaintTracking::Global<StoredXssTrackingConfig>;
|
||||||
StoredTaintTrackingConfiguration c, DataFlow2::PathNode source, DataFlow2::PathNode sink,
|
|
||||||
string explanation
|
from StoredXss::PathNode source, StoredXss::PathNode sink, string explanation
|
||||||
where
|
where
|
||||||
c.hasFlowPath(source, sink) and
|
StoredXss::flowPath(source, sink) and
|
||||||
if exists(sink.getNode().(Sink).explanation())
|
if exists(sink.getNode().(Sink).explanation())
|
||||||
then explanation = " (" + sink.getNode().(Sink).explanation() + ")"
|
then explanation = " (" + sink.getNode().(Sink).explanation() + ")"
|
||||||
else explanation = ""
|
else explanation = ""
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import csharp
|
import csharp
|
||||||
import semmle.code.csharp.frameworks.Test
|
import semmle.code.csharp.frameworks.Test
|
||||||
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
|
import Random::InsecureRandomness::PathGraph
|
||||||
|
|
||||||
module Random {
|
module Random {
|
||||||
import semmle.code.csharp.security.dataflow.flowsources.Remote
|
import semmle.code.csharp.security.dataflow.flowsources.Remote
|
||||||
@@ -38,21 +38,24 @@ module Random {
|
|||||||
/**
|
/**
|
||||||
* A taint-tracking configuration for insecure randomness in security sensitive context.
|
* A taint-tracking configuration for insecure randomness in security sensitive context.
|
||||||
*/
|
*/
|
||||||
class TaintTrackingConfiguration extends TaintTracking::Configuration {
|
module InsecureRandomnessConfig implements DataFlow::ConfigSig {
|
||||||
TaintTrackingConfiguration() { this = "RandomDataFlowConfiguration" }
|
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
// succ = array_or_indexer[pred] - use of random numbers in an index
|
// succ = array_or_indexer[pred] - use of random numbers in an index
|
||||||
succ.asExpr().(ElementAccess).getAnIndex() = pred.asExpr()
|
succ.asExpr().(ElementAccess).getAnIndex() = pred.asExpr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint-tracking module for insecure randomness in security sensitive context.
|
||||||
|
*/
|
||||||
|
module InsecureRandomness = TaintTracking::Global<InsecureRandomnessConfig>;
|
||||||
|
|
||||||
/** A source of cryptographically insecure random numbers. */
|
/** A source of cryptographically insecure random numbers. */
|
||||||
class RandomSource extends Source {
|
class RandomSource extends Source {
|
||||||
RandomSource() {
|
RandomSource() {
|
||||||
@@ -112,10 +115,8 @@ module Random {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from
|
from Random::InsecureRandomness::PathNode source, Random::InsecureRandomness::PathNode sink
|
||||||
Random::TaintTrackingConfiguration randomTracking, DataFlow::PathNode source,
|
where Random::InsecureRandomness::flowPath(source, sink)
|
||||||
DataFlow::PathNode sink
|
|
||||||
where randomTracking.hasFlowPath(source, sink)
|
|
||||||
select sink.getNode(), source, sink,
|
select sink.getNode(), source, sink,
|
||||||
"This uses a cryptographically insecure random number generated at $@ in a security context.",
|
"This uses a cryptographically insecure random number generated at $@ in a security context.",
|
||||||
source.getNode(), source.getNode().toString()
|
source.getNode(), source.getNode().toString()
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
|
|
||||||
import csharp
|
import csharp
|
||||||
import RequestForgery::RequestForgery
|
import RequestForgery::RequestForgery
|
||||||
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
|
import RequestForgeryFlow::PathGraph
|
||||||
|
|
||||||
from RequestForgeryConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
|
from RequestForgeryFlow::PathNode source, RequestForgeryFlow::PathNode sink
|
||||||
where c.hasFlowPath(source, sink)
|
where RequestForgeryFlow::flowPath(source, sink)
|
||||||
select sink.getNode(), source, sink, "The URL of this request depends on a $@.", source.getNode(),
|
select sink.getNode(), source, sink, "The URL of this request depends on a $@.", source.getNode(),
|
||||||
"user-provided value"
|
"user-provided value"
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ module RequestForgery {
|
|||||||
abstract private class Barrier extends DataFlow::Node { }
|
abstract private class Barrier extends DataFlow::Node { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED: Use `RequestForgeryFlow` instead.
|
||||||
|
*
|
||||||
* A data flow configuration for detecting server side request forgery vulnerabilities.
|
* A data flow configuration for detecting server side request forgery vulnerabilities.
|
||||||
*/
|
*/
|
||||||
class RequestForgeryConfiguration extends DataFlow::Configuration {
|
deprecated class RequestForgeryConfiguration extends DataFlow::Configuration {
|
||||||
RequestForgeryConfiguration() { this = "Server Side Request forgery" }
|
RequestForgeryConfiguration() { this = "Server Side Request forgery" }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
@@ -54,6 +56,40 @@ module RequestForgery {
|
|||||||
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
|
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data flow configuration for detecting server side request forgery vulnerabilities.
|
||||||
|
*/
|
||||||
|
private module RequestForgeryFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node prev, DataFlow::Node succ) {
|
||||||
|
interpolatedStringFlowStep(prev, succ)
|
||||||
|
or
|
||||||
|
stringReplaceStep(prev, succ)
|
||||||
|
or
|
||||||
|
uriCreationStep(prev, succ)
|
||||||
|
or
|
||||||
|
formatConvertStep(prev, succ)
|
||||||
|
or
|
||||||
|
toStringStep(prev, succ)
|
||||||
|
or
|
||||||
|
stringConcatStep(prev, succ)
|
||||||
|
or
|
||||||
|
stringFormatStep(prev, succ)
|
||||||
|
or
|
||||||
|
pathCombineStep(prev, succ)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data flow module for detecting server side request forgery vulnerabilities.
|
||||||
|
*/
|
||||||
|
module RequestForgeryFlow = DataFlow::Global<RequestForgeryFlowConfig>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A remote data flow source taken as a source
|
* A remote data flow source taken as a source
|
||||||
* for Server Side Request Forgery(SSRF) Vulnerabilities.
|
* for Server Side Request Forgery(SSRF) Vulnerabilities.
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ where
|
|||||||
iResponse.getAppendMethod() = mc.getTarget() and
|
iResponse.getAppendMethod() = mc.getTarget() and
|
||||||
isCookieWithSensitiveName(mc.getArgument(0)) and
|
isCookieWithSensitiveName(mc.getArgument(0)) and
|
||||||
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
|
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
|
||||||
not exists(OnAppendCookieHttpOnlyTrackingConfig config | config.hasFlowTo(_)) and
|
not OnAppendCookieHttpOnlyTracking::flowTo(_) and
|
||||||
// Passed as third argument to `IResponseCookies.Append`
|
// Passed as third argument to `IResponseCookies.Append`
|
||||||
exists(DataFlow::Node creation, DataFlow::Node append |
|
exists(DataFlow::Node creation, DataFlow::Node append |
|
||||||
CookieOptionsTracking::flow(creation, append) and
|
CookieOptionsTracking::flow(creation, append) and
|
||||||
@@ -67,7 +67,7 @@ where
|
|||||||
// default is not configured or is not set to `Always`
|
// default is not configured or is not set to `Always`
|
||||||
not getAValueForCookiePolicyProp("HttpOnly").getValue() = "1" and
|
not getAValueForCookiePolicyProp("HttpOnly").getValue() = "1" and
|
||||||
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
|
// there is no callback `OnAppendCookie` that sets `HttpOnly` to true
|
||||||
not exists(OnAppendCookieHttpOnlyTrackingConfig config | config.hasFlowTo(_)) and
|
not OnAppendCookieHttpOnlyTracking::flowTo(_) and
|
||||||
iResponse.getAppendMethod() = mc.getTarget() and
|
iResponse.getAppendMethod() = mc.getTarget() and
|
||||||
isCookieWithSensitiveName(mc.getArgument(0)) and
|
isCookieWithSensitiveName(mc.getArgument(0)) and
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ where
|
|||||||
getAValueForCookiePolicyProp("Secure").getValue() = "1"
|
getAValueForCookiePolicyProp("Secure").getValue() = "1"
|
||||||
) and
|
) and
|
||||||
// there is no callback `OnAppendCookie` that sets `Secure` to true
|
// there is no callback `OnAppendCookie` that sets `Secure` to true
|
||||||
not exists(OnAppendCookieSecureTrackingConfig config | config.hasFlowTo(_)) and
|
not OnAppendCookieSecureTracking::flowTo(_) and
|
||||||
(
|
(
|
||||||
// `Secure` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
|
// `Secure` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
|
||||||
exists(ObjectCreation oc |
|
exists(ObjectCreation oc |
|
||||||
@@ -80,7 +80,7 @@ where
|
|||||||
or
|
or
|
||||||
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
|
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
|
||||||
// there is no callback `OnAppendCookie` that sets `Secure` to true
|
// there is no callback `OnAppendCookie` that sets `Secure` to true
|
||||||
not exists(OnAppendCookieSecureTrackingConfig config | config.hasFlowTo(_)) and
|
not OnAppendCookieSecureTracking::flowTo(_) and
|
||||||
// the cookie option is passed to `Append`
|
// the cookie option is passed to `Append`
|
||||||
exists(DataFlow::Node creation |
|
exists(DataFlow::Node creation |
|
||||||
CookieOptionsTracking::flow(creation, _) and
|
CookieOptionsTracking::flow(creation, _) and
|
||||||
|
|||||||
@@ -102,21 +102,6 @@ private class TokenValidationResultIsValidCall extends PropertyRead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Dataflow from the output of `Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateToken` call to access the `IsValid` or `Exception` property
|
|
||||||
*/
|
|
||||||
private class FlowsToTokenValidationResultIsValidCall extends DataFlow::Configuration {
|
|
||||||
FlowsToTokenValidationResultIsValidCall() { this = "FlowsToTokenValidationResultIsValidCall" }
|
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) {
|
|
||||||
source.asExpr() instanceof JsonWebTokenHandlerValidateTokenCall
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node sink) {
|
|
||||||
exists(TokenValidationResultIsValidCall call | sink.asExpr() = call.getQualifier())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A security-sensitive property for `Microsoft.IdentityModel.Tokens.TokenValidationParameters`
|
* A security-sensitive property for `Microsoft.IdentityModel.Tokens.TokenValidationParameters`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,18 +9,16 @@ import semmle.code.csharp.frameworks.microsoft.AspNetCore
|
|||||||
* Holds if the expression is a variable with a sensitive name.
|
* Holds if the expression is a variable with a sensitive name.
|
||||||
*/
|
*/
|
||||||
predicate isCookieWithSensitiveName(Expr cookieExpr) {
|
predicate isCookieWithSensitiveName(Expr cookieExpr) {
|
||||||
exists(AuthCookieNameConfiguration dataflow, DataFlow::Node sink |
|
exists(DataFlow::Node sink |
|
||||||
dataflow.hasFlowTo(sink) and
|
AuthCookieName::flowTo(sink) and
|
||||||
sink.asExpr() = cookieExpr
|
sink.asExpr() = cookieExpr
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks if a variable with a sensitive name is used as an argument.
|
* Configuration for tracking if a variable with a sensitive name is used as an argument.
|
||||||
*/
|
*/
|
||||||
private class AuthCookieNameConfiguration extends DataFlow::Configuration {
|
private module AuthCookieNameConfig implements DataFlow::ConfigSig {
|
||||||
AuthCookieNameConfiguration() { this = "AuthCookieNameConfiguration" }
|
|
||||||
|
|
||||||
private predicate isAuthVariable(Expr expr) {
|
private predicate isAuthVariable(Expr expr) {
|
||||||
exists(string val |
|
exists(string val |
|
||||||
(
|
(
|
||||||
@@ -32,13 +30,16 @@ private class AuthCookieNameConfiguration extends DataFlow::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) }
|
predicate isSource(DataFlow::Node source) { isAuthVariable(source.asExpr()) }
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node sink) {
|
predicate isSink(DataFlow::Node sink) { exists(Call c | sink.asExpr() = c.getAnArgument()) }
|
||||||
exists(Call c | sink.asExpr() = c.getAnArgument())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks if a variable with a sensitive name is used as an argument.
|
||||||
|
*/
|
||||||
|
private module AuthCookieName = DataFlow::Global<AuthCookieNameConfig>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEPRECATED: Use `CookieOptionsTracking` instead.
|
* DEPRECATED: Use `CookieOptionsTracking` instead.
|
||||||
*
|
*
|
||||||
@@ -134,18 +135,22 @@ Expr getAValueForProp(ObjectCreation create, Assignment a, string prop) {
|
|||||||
predicate isPropertySet(ObjectCreation oc, string prop) { exists(getAValueForProp(oc, _, prop)) }
|
predicate isPropertySet(ObjectCreation oc, string prop) { exists(getAValueForProp(oc, _, prop)) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED: Use `OnAppendCookieSecureTracking` instead.
|
||||||
|
*
|
||||||
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
|
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
|
||||||
*/
|
*/
|
||||||
class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
|
deprecated class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||||
OnAppendCookieSecureTrackingConfig() { this = "OnAppendCookieSecureTrackingConfig" }
|
OnAppendCookieSecureTrackingConfig() { this = "OnAppendCookieSecureTrackingConfig" }
|
||||||
|
|
||||||
override string propertyName() { result = "Secure" }
|
override string propertyName() { result = "Secure" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED: Use `OnAppendCookieHttpOnlyTracking` instead.
|
||||||
|
*
|
||||||
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
|
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
|
||||||
*/
|
*/
|
||||||
class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
|
deprecated class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||||
OnAppendCookieHttpOnlyTrackingConfig() { this = "OnAppendCookieHttpOnlyTrackingConfig" }
|
OnAppendCookieHttpOnlyTrackingConfig() { this = "OnAppendCookieHttpOnlyTrackingConfig" }
|
||||||
|
|
||||||
override string propertyName() { result = "HttpOnly" }
|
override string propertyName() { result = "HttpOnly" }
|
||||||
@@ -205,3 +210,83 @@ abstract private class OnAppendCookieTrackingConfig extends DataFlow::Configurat
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private signature string propertyName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for tracking if a callback used in `OnAppendCookie` sets a cookie property to `true`.
|
||||||
|
*/
|
||||||
|
private module OnAppendCookieTrackingConfig<propertyName/0 getPropertyName> implements
|
||||||
|
DataFlow::ConfigSig
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Specifies the cookie property name to track.
|
||||||
|
*/
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
exists(PropertyWrite pw, Assignment delegateAssign, Callable c |
|
||||||
|
pw.getProperty().getName() = "OnAppendCookie" and
|
||||||
|
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreBuilderCookiePolicyOptions and
|
||||||
|
delegateAssign.getLValue() = pw and
|
||||||
|
(
|
||||||
|
exists(LambdaExpr lambda |
|
||||||
|
delegateAssign.getRValue() = lambda and
|
||||||
|
lambda = c
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(DelegateCreation delegate |
|
||||||
|
delegateAssign.getRValue() = delegate and
|
||||||
|
delegate.getArgument().(CallableAccess).getTarget() = c
|
||||||
|
)
|
||||||
|
) and
|
||||||
|
c.getParameter(0) = source.asParameter()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(PropertyWrite pw, Assignment a |
|
||||||
|
pw.getProperty().getDeclaringType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
|
||||||
|
pw.getProperty().getName() = getPropertyName() and
|
||||||
|
a.getLValue() = pw and
|
||||||
|
exists(Expr val |
|
||||||
|
DataFlow::localExprFlow(val, a.getRValue()) and
|
||||||
|
val.getValue() = "true"
|
||||||
|
) and
|
||||||
|
sink.asExpr() = pw.getQualifier()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node2.asExpr() =
|
||||||
|
any(PropertyRead pr |
|
||||||
|
pr.getQualifier() = node1.asExpr() and
|
||||||
|
pr.getProperty().getDeclaringType() instanceof
|
||||||
|
MicrosoftAspNetCoreCookiePolicyAppendCookieContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string getPropertyNameSecure() { result = "Secure" }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `Secure` to `true`.
|
||||||
|
*/
|
||||||
|
private module OnAppendCookieSecureTrackingConfig =
|
||||||
|
OnAppendCookieTrackingConfig<getPropertyNameSecure/0>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks if a callback used in `OnAppendCookie` sets `Secure` to `true`.
|
||||||
|
*/
|
||||||
|
module OnAppendCookieSecureTracking = DataFlow::Global<OnAppendCookieSecureTrackingConfig>;
|
||||||
|
|
||||||
|
private string getPropertyNameHttpOnly() { result = "HttpOnly" }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration module for tracking if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
|
||||||
|
*/
|
||||||
|
private module OnAppendCookieHttpOnlyTrackingConfig =
|
||||||
|
OnAppendCookieTrackingConfig<getPropertyNameHttpOnly/0>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
|
||||||
|
*/
|
||||||
|
module OnAppendCookieHttpOnlyTracking = DataFlow::Global<OnAppendCookieHttpOnlyTrackingConfig>;
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ import semmle.code.java.dataflow.TaintTracking
|
|||||||
import TestUtilities.InlineExpectationsTest
|
import TestUtilities.InlineExpectationsTest
|
||||||
private import semmle.code.java.dataflow.ExternalFlow
|
private import semmle.code.java.dataflow.ExternalFlow
|
||||||
|
|
||||||
class Config extends TaintTracking::Configuration {
|
module Config implements DataFlow::ConfigSig {
|
||||||
Config() { this = "Config" }
|
predicate isSource(DataFlow::Node n) {
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node n) {
|
|
||||||
n.asExpr().(MethodAccess).getCallee().getName() = "source"
|
n.asExpr().(MethodAccess).getCallee().getName() = "source"
|
||||||
or
|
or
|
||||||
sourceNode(n, "kotlinMadFlowTest")
|
sourceNode(n, "kotlinMadFlowTest")
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node n) {
|
predicate isSink(DataFlow::Node n) {
|
||||||
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
||||||
or
|
or
|
||||||
sinkNode(n, "kotlinMadFlowTest")
|
sinkNode(n, "kotlinMadFlowTest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module Flow = TaintTracking::Global<Config>;
|
||||||
|
|
||||||
class InlineFlowTest extends InlineExpectationsTest {
|
class InlineFlowTest extends InlineExpectationsTest {
|
||||||
InlineFlowTest() { this = "HasFlowTest" }
|
InlineFlowTest() { this = "HasFlowTest" }
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
|||||||
|
|
||||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||||
tag = "flow" and
|
tag = "flow" and
|
||||||
exists(DataFlow::Node sink, Config c | c.hasFlowTo(sink) |
|
exists(DataFlow::Node sink | Flow::flowTo(sink) |
|
||||||
sink.getLocation() = location and
|
sink.getLocation() = location and
|
||||||
element = sink.toString() and
|
element = sink.toString() and
|
||||||
value = ""
|
value = ""
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ query predicate superAccesses(
|
|||||||
enclosingType = enclosingCallable.getDeclaringType()
|
enclosingType = enclosingCallable.getDeclaringType()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Config extends DataFlow::Configuration {
|
module Config implements DataFlow::ConfigSig {
|
||||||
Config() { this = "testconfig" }
|
predicate isSource(DataFlow::Node x) {
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node x) {
|
|
||||||
x.asExpr() instanceof IntegerLiteral and x.getEnclosingCallable().fromSource()
|
x.asExpr() instanceof IntegerLiteral and x.getEnclosingCallable().fromSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node x) {
|
predicate isSink(DataFlow::Node x) {
|
||||||
x.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
x.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from Config c, DataFlow::Node source, DataFlow::Node sink
|
module Flow = DataFlow::Global<Config>;
|
||||||
where c.hasFlow(source, sink)
|
|
||||||
|
from DataFlow::Node source, DataFlow::Node sink
|
||||||
|
where Flow::flow(source, sink)
|
||||||
select source, sink
|
select source, sink
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import java
|
import java
|
||||||
import semmle.code.java.dataflow.DataFlow
|
import semmle.code.java.dataflow.DataFlow
|
||||||
import DataFlow::PathGraph
|
import Flow::PathGraph
|
||||||
|
|
||||||
class Config extends DataFlow::Configuration {
|
module Config implements DataFlow::ConfigSig {
|
||||||
Config() { this = "Config" }
|
predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
|
predicate isSink(DataFlow::Node n) {
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node n) {
|
|
||||||
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from DataFlow::PathNode source, DataFlow::PathNode sink, Config c
|
module Flow = DataFlow::Global<Config>;
|
||||||
where c.hasFlowPath(source, sink)
|
|
||||||
|
from Flow::PathNode source, Flow::PathNode sink
|
||||||
|
where Flow::flowPath(source, sink)
|
||||||
select source, source, sink, "flow path"
|
select source, source, sink, "flow path"
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ private J::Method superImpl(J::Method m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private predicate isInTestFile(J::File file) {
|
private predicate isInTestFile(J::File file) {
|
||||||
file.getAbsolutePath().matches("%src/test/%") or
|
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
|
||||||
file.getAbsolutePath().matches("%/guava-tests/%") or
|
not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
|
||||||
file.getAbsolutePath().matches("%/guava-testlib/%")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private predicate isJdkInternal(J::CompilationUnit cu) {
|
private predicate isJdkInternal(J::CompilationUnit cu) {
|
||||||
|
|||||||
@@ -1,284 +1,28 @@
|
|||||||
package com.semmle.js.extractor;
|
package com.semmle.js.extractor;
|
||||||
|
|
||||||
import com.semmle.util.data.StringUtil;
|
|
||||||
import com.semmle.util.exception.CatastrophicError;
|
|
||||||
import com.semmle.util.exception.UserError;
|
|
||||||
import com.semmle.util.locations.LineTable;
|
|
||||||
import com.semmle.util.trap.TrapWriter;
|
|
||||||
import com.semmle.util.trap.TrapWriter.Label;
|
|
||||||
import com.semmle.util.trap.TrapWriter.Table;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.yaml.snakeyaml.composer.Composer;
|
import com.semmle.extractor.yaml.YamlPopulator;
|
||||||
import org.yaml.snakeyaml.error.Mark;
|
|
||||||
import org.yaml.snakeyaml.error.MarkedYAMLException;
|
|
||||||
import org.yaml.snakeyaml.events.AliasEvent;
|
|
||||||
import org.yaml.snakeyaml.events.Event;
|
|
||||||
import org.yaml.snakeyaml.events.MappingStartEvent;
|
|
||||||
import org.yaml.snakeyaml.events.NodeEvent;
|
|
||||||
import org.yaml.snakeyaml.events.ScalarEvent;
|
|
||||||
import org.yaml.snakeyaml.events.SequenceStartEvent;
|
|
||||||
import org.yaml.snakeyaml.nodes.NodeId;
|
|
||||||
import org.yaml.snakeyaml.parser.Parser;
|
|
||||||
import org.yaml.snakeyaml.parser.ParserImpl;
|
|
||||||
import org.yaml.snakeyaml.reader.ReaderException;
|
|
||||||
import org.yaml.snakeyaml.reader.StreamReader;
|
|
||||||
import org.yaml.snakeyaml.resolver.Resolver;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extractor for populating YAML files.
|
* Extractor for populating YAML files.
|
||||||
*
|
*
|
||||||
* <p>The extractor uses <a href="http://www.snakeyaml.org/">SnakeYAML</a> to parse YAML.
|
* <p>
|
||||||
|
* The extractor uses <a href="http://www.snakeyaml.org/">SnakeYAML</a> to parse
|
||||||
|
* YAML.
|
||||||
*/
|
*/
|
||||||
public class YAMLExtractor implements IExtractor {
|
public class YAMLExtractor implements IExtractor {
|
||||||
/** The tables constituting the YAML dbscheme. */
|
|
||||||
private static enum YAMLTables implements Table {
|
|
||||||
YAML(6), // yaml (id: @yaml_node, kind: int ref, parent: @yaml_node_parent ref,
|
|
||||||
// idx: int ref, tag: string ref, tostring: string ref)
|
|
||||||
YAML_ANCHORS(2), // yaml_anchors (node: @yaml_node ref, anchor: string ref)
|
|
||||||
YAML_ALIASES(2), // yaml_aliases (alias: @yaml_alias_node ref, target: string ref)
|
|
||||||
YAML_SCALARS(
|
|
||||||
3), // yaml_scalars (scalar: @yaml_scalar_node ref, style: int ref, value: string ref)
|
|
||||||
YAML_ERRORS(2); // yaml_errors (id: @yaml_error, message: string ref)
|
|
||||||
|
|
||||||
private final int arity;
|
|
||||||
|
|
||||||
private YAMLTables(int arity) {
|
|
||||||
this.arity = arity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return StringUtil.lc(name());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getArity() {
|
|
||||||
return arity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validate(Object... values) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* case @yaml_node.kind of
|
|
||||||
* 0 = @yaml_scalar_node
|
|
||||||
* | 1 = @yaml_mapping_node
|
|
||||||
* | 2 = @yaml_sequence_node
|
|
||||||
* | 3 = @yaml_alias_node
|
|
||||||
*/
|
|
||||||
private static enum NodeKind {
|
|
||||||
SCALAR,
|
|
||||||
MAPPING,
|
|
||||||
SEQUENCE,
|
|
||||||
ALIAS
|
|
||||||
};
|
|
||||||
|
|
||||||
private final boolean tolerateParseErrors;
|
private final boolean tolerateParseErrors;
|
||||||
|
|
||||||
private TextualExtractor textualExtractor;
|
|
||||||
private LocationManager locationManager;
|
|
||||||
private TrapWriter trapWriter;
|
|
||||||
private LineTable lineTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The underlying SnakeYAML parser; we use the relatively low-level {@linkplain Parser} instead of
|
|
||||||
* the more high-level {@linkplain Composer}, since our dbscheme represents YAML documents in AST
|
|
||||||
* form, with aliases left unresolved.
|
|
||||||
*/
|
|
||||||
private Parser parser;
|
|
||||||
|
|
||||||
/** The resolver used for resolving type tags. */
|
|
||||||
private Resolver resolver;
|
|
||||||
|
|
||||||
public YAMLExtractor(ExtractorConfig config) {
|
public YAMLExtractor(ExtractorConfig config) {
|
||||||
this.tolerateParseErrors = config.isTolerateParseErrors();
|
this.tolerateParseErrors = config.isTolerateParseErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LineTable getLineTable() {
|
|
||||||
if (lineTable == null) {
|
|
||||||
lineTable = new LineTable(this.textualExtractor.getSource());
|
|
||||||
}
|
|
||||||
return lineTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParseResultInfo extract(TextualExtractor textualExtractor) {
|
public ParseResultInfo extract(TextualExtractor textualExtractor) {
|
||||||
this.textualExtractor = textualExtractor;
|
new YamlPopulator(textualExtractor.getExtractedFile(), textualExtractor.getSource(),
|
||||||
locationManager = textualExtractor.getLocationManager();
|
textualExtractor.getTrapwriter(),
|
||||||
trapWriter = textualExtractor.getTrapwriter();
|
this.tolerateParseErrors).extract();
|
||||||
|
|
||||||
Label fileLabel = locationManager.getFileLabel();
|
|
||||||
locationManager.setHasLocationTable("yaml_locations");
|
|
||||||
try {
|
|
||||||
parser = new ParserImpl(new StreamReader(textualExtractor.getSource()));
|
|
||||||
resolver = new Resolver();
|
|
||||||
int idx = 0;
|
|
||||||
while (!atStreamEnd())
|
|
||||||
extractDocument(fileLabel, idx++, textualExtractor.getSource().codePoints().toArray());
|
|
||||||
} catch (MarkedYAMLException e) {
|
|
||||||
int line = e.getProblemMark().getLine() + 1;
|
|
||||||
int column = e.getProblemMark().getColumn() + 1;
|
|
||||||
if (!this.tolerateParseErrors)
|
|
||||||
throw new UserError(e.getProblem() + ": " + line + ":" + column);
|
|
||||||
Label lbl = trapWriter.freshLabel();
|
|
||||||
trapWriter.addTuple(YAMLTables.YAML_ERRORS, lbl, e.getProblem());
|
|
||||||
locationManager.emitSnippetLocation(lbl, line, column, line, column);
|
|
||||||
} catch (ReaderException e) {
|
|
||||||
if (!this.tolerateParseErrors) throw new UserError(e.toString());
|
|
||||||
int c = e.getCodePoint();
|
|
||||||
String s = String.valueOf(Character.toChars(c));
|
|
||||||
trapWriter.addTuple(
|
|
||||||
YAMLTables.YAML_ERRORS,
|
|
||||||
trapWriter.freshLabel(),
|
|
||||||
"Unexpected character " + s + "(" + c + ")");
|
|
||||||
// unfortunately, SnakeYAML does not provide structured location information for
|
|
||||||
// ReaderExceptions
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParseResultInfo(0, 0, Collections.emptyList());
|
return new ParseResultInfo(0, 0, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check whether the parser has encountered the end of the YAML input stream. */
|
|
||||||
private boolean atStreamEnd() {
|
|
||||||
if (parser.checkEvent(Event.ID.StreamStart)) parser.getEvent();
|
|
||||||
return parser.checkEvent(Event.ID.StreamEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extract a complete YAML document; cf. {@link Composer#getNode}. */
|
|
||||||
private void extractDocument(Label parent, int idx, int[] codepoints) {
|
|
||||||
// Drop the DOCUMENT-START event
|
|
||||||
parser.getEvent();
|
|
||||||
extractNode(parent, idx, codepoints);
|
|
||||||
// Drop the DOCUMENT-END event
|
|
||||||
parser.getEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extract a single YAML node; cf. {@link Composer#composeNode}. */
|
|
||||||
private void extractNode(Label parent, int idx, int[] codepoints) {
|
|
||||||
Label label = trapWriter.freshLabel();
|
|
||||||
NodeKind kind;
|
|
||||||
String tag = "";
|
|
||||||
Event start = parser.getEvent(), end = start;
|
|
||||||
|
|
||||||
if (start.is(Event.ID.Alias)) {
|
|
||||||
kind = NodeKind.ALIAS;
|
|
||||||
trapWriter.addTuple(YAMLTables.YAML_ALIASES, label, ((AliasEvent) start).getAnchor());
|
|
||||||
} else {
|
|
||||||
String anchor = start instanceof NodeEvent ? ((NodeEvent) start).getAnchor() : null;
|
|
||||||
if (anchor != null) trapWriter.addTuple(YAMLTables.YAML_ANCHORS, label, anchor);
|
|
||||||
|
|
||||||
if (start.is(Event.ID.Scalar)) {
|
|
||||||
kind = NodeKind.SCALAR;
|
|
||||||
ScalarEvent scalar = (ScalarEvent) start;
|
|
||||||
tag =
|
|
||||||
getTag(
|
|
||||||
scalar.getTag(),
|
|
||||||
NodeId.scalar,
|
|
||||||
scalar.getValue(),
|
|
||||||
scalar.getImplicit().canOmitTagInPlainScalar());
|
|
||||||
Character style = scalar.getStyle();
|
|
||||||
int styleCode = style == null ? 0 : (int) style;
|
|
||||||
trapWriter.addTuple(YAMLTables.YAML_SCALARS, label, styleCode, scalar.getValue());
|
|
||||||
} else if (start.is(Event.ID.SequenceStart)) {
|
|
||||||
kind = NodeKind.SEQUENCE;
|
|
||||||
SequenceStartEvent sequenceStart = (SequenceStartEvent) start;
|
|
||||||
tag = getTag(sequenceStart.getTag(), NodeId.sequence, null, sequenceStart.getImplicit());
|
|
||||||
|
|
||||||
int childIdx = 0;
|
|
||||||
while (!parser.checkEvent(Event.ID.SequenceEnd)) extractNode(label, childIdx++, codepoints);
|
|
||||||
|
|
||||||
end = parser.getEvent();
|
|
||||||
} else if (start.is(Event.ID.MappingStart)) {
|
|
||||||
kind = NodeKind.MAPPING;
|
|
||||||
MappingStartEvent mappingStart = (MappingStartEvent) start;
|
|
||||||
tag = getTag(mappingStart.getTag(), NodeId.mapping, null, mappingStart.getImplicit());
|
|
||||||
|
|
||||||
int childIdx = 1;
|
|
||||||
while (!parser.checkEvent(Event.ID.MappingEnd)) {
|
|
||||||
extractNode(label, childIdx, codepoints);
|
|
||||||
extractNode(label, -childIdx, codepoints);
|
|
||||||
++childIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
end = parser.getEvent();
|
|
||||||
} else {
|
|
||||||
throw new CatastrophicError("Unexpected YAML parser event: " + start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trapWriter.addTuple(
|
|
||||||
YAMLTables.YAML,
|
|
||||||
label,
|
|
||||||
kind.ordinal(),
|
|
||||||
parent,
|
|
||||||
idx,
|
|
||||||
tag,
|
|
||||||
mkToString(start.getStartMark(), end.getEndMark(), codepoints));
|
|
||||||
extractLocation(label, start.getStartMark(), end.getEndMark());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Determine the type tag of a node. */
|
|
||||||
private String getTag(String explicitTag, NodeId kind, String value, boolean implicit) {
|
|
||||||
if (explicitTag == null || "!".equals(explicitTag))
|
|
||||||
return resolver.resolve(kind, value, implicit).getValue();
|
|
||||||
return explicitTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isNewLine(int codePoint) {
|
|
||||||
switch (codePoint) {
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case '\u0085':
|
|
||||||
case '\u2028':
|
|
||||||
case '\u2029':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SnakeYAML doesn't directly expose the source text of nodes, but we also take the file contents
|
|
||||||
* as an array of Unicode code points. The start and end marks each contain an index into the code
|
|
||||||
* point stream (the end is exclusive), so we can reconstruct the snippet. For readability, we
|
|
||||||
* stop at the first encountered newline.
|
|
||||||
*/
|
|
||||||
private static String mkToString(Mark startMark, Mark endMark, int[] codepoints) {
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
for (int i = startMark.getIndex(); i < endMark.getIndex() && !isNewLine(codepoints[i]); i++)
|
|
||||||
b.appendCodePoint(codepoints[i]);
|
|
||||||
return TextualExtractor.sanitiseToString(b.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Emit a source location for a YAML node. */
|
|
||||||
private void extractLocation(Label label, Mark startMark, Mark endMark) {
|
|
||||||
int startLine, startColumn, endLine, endColumn;
|
|
||||||
|
|
||||||
// SnakeYAML uses 0-based indexing for both lines and columns, so need to +1
|
|
||||||
startLine = startMark.getLine() + 1;
|
|
||||||
startColumn = startMark.getColumn() + 1;
|
|
||||||
|
|
||||||
// SnakeYAML's end positions are exclusive, so only need to +1 for the line
|
|
||||||
endLine = endMark.getLine() + 1;
|
|
||||||
endColumn = endMark.getColumn();
|
|
||||||
|
|
||||||
// Avoid emitting column zero for non-empty locations
|
|
||||||
if (endColumn == 0 && !(startLine == endLine && startColumn == endColumn)) {
|
|
||||||
String source = textualExtractor.getSource();
|
|
||||||
int offset = getLineTable().getOffsetFromPoint(endMark.getLine(), endMark.getColumn()) - 1;
|
|
||||||
while (offset > 0 && isNewLine((int)source.charAt(offset))) {
|
|
||||||
--offset;
|
|
||||||
}
|
|
||||||
com.semmle.util.locations.Position adjustedEndPos = getLineTable().getEndPositionFromOffset(offset);
|
|
||||||
endLine = adjustedEndPos.getLine();
|
|
||||||
endColumn = adjustedEndPos.getColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
locationManager.emitSnippetLocation(label, startLine, startColumn, endLine, endColumn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @name SimpleXMLRPCServer DoS vulnerability
|
* @name SimpleXMLRPCServer denial of service
|
||||||
* @description SimpleXMLRPCServer is vulnerable to DoS attacks from untrusted user input
|
* @description SimpleXMLRPCServer is vulnerable to denial of service attacks from untrusted user input
|
||||||
* @kind problem
|
* @kind problem
|
||||||
* @problem.severity warning
|
* @problem.severity warning
|
||||||
* @precision high
|
* @precision high
|
||||||
|
|||||||
BIN
ql/Cargo.lock
generated
BIN
ql/Cargo.lock
generated
Binary file not shown.
@@ -9,4 +9,4 @@ edition = "2018"
|
|||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
chrono = "0.4.24"
|
chrono = "0.4.24"
|
||||||
rayon = "1.7.0"
|
rayon = "1.7.0"
|
||||||
regex = "1.7.3"
|
regex = "1.8.1"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ tree-sitter-blame = {path = "../buramu/tree-sitter-blame"}
|
|||||||
tree-sitter-json = {git = "https://github.com/tausbn/tree-sitter-json.git", rev = "745663ee997f1576fe1e7187e6347e0db36ec7a9"}
|
tree-sitter-json = {git = "https://github.com/tausbn/tree-sitter-json.git", rev = "745663ee997f1576fe1e7187e6347e0db36ec7a9"}
|
||||||
clap = { version = "4.2", features = ["derive"] }
|
clap = { version = "4.2", features = ["derive"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
rayon = "1.7.0"
|
rayon = "1.7.0"
|
||||||
regex = "1.7.3"
|
regex = "1.8.1"
|
||||||
codeql-extractor = { path = "../../shared/tree-sitter-extractor" }
|
codeql-extractor = { path = "../../shared/tree-sitter-extractor" }
|
||||||
|
|||||||
@@ -359,6 +359,11 @@ module API {
|
|||||||
Location getLocation() {
|
Location getLocation() {
|
||||||
result = this.getInducingNode().getLocation()
|
result = this.getInducingNode().getLocation()
|
||||||
or
|
or
|
||||||
|
exists(DataFlow::ModuleNode mod |
|
||||||
|
this = Impl::MkModuleObject(mod) and
|
||||||
|
result = mod.getLocation()
|
||||||
|
)
|
||||||
|
or
|
||||||
// For nodes that do not have a meaningful location, `path` is the empty string and all other
|
// For nodes that do not have a meaningful location, `path` is the empty string and all other
|
||||||
// parameters are zero.
|
// parameters are zero.
|
||||||
not exists(this.getInducingNode()) and
|
not exists(this.getInducingNode()) and
|
||||||
@@ -601,7 +606,9 @@ module API {
|
|||||||
/** A use of an API member at the node `nd`. */
|
/** A use of an API member at the node `nd`. */
|
||||||
MkUse(DataFlow::Node nd) { isUse(nd) } or
|
MkUse(DataFlow::Node nd) { isUse(nd) } or
|
||||||
/** A value that escapes into an external library at the node `nd` */
|
/** A value that escapes into an external library at the node `nd` */
|
||||||
MkDef(DataFlow::Node nd) { isDef(nd) }
|
MkDef(DataFlow::Node nd) { isDef(nd) } or
|
||||||
|
/** A module object seen as a use node. */
|
||||||
|
MkModuleObject(DataFlow::ModuleNode mod)
|
||||||
|
|
||||||
private string resolveTopLevel(ConstantReadAccess read) {
|
private string resolveTopLevel(ConstantReadAccess read) {
|
||||||
result = read.getModule().getQualifiedName() and
|
result = read.getModule().getQualifiedName() and
|
||||||
@@ -684,7 +691,14 @@ module API {
|
|||||||
* Holds if `ref` is a use of node `nd`.
|
* Holds if `ref` is a use of node `nd`.
|
||||||
*/
|
*/
|
||||||
cached
|
cached
|
||||||
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
|
predicate use(TApiNode nd, DataFlow::Node ref) {
|
||||||
|
nd = MkUse(ref)
|
||||||
|
or
|
||||||
|
exists(DataFlow::ModuleNode mod |
|
||||||
|
nd = MkModuleObject(mod) and
|
||||||
|
ref = mod.getAnImmediateReference()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if `rhs` is a RHS of node `nd`.
|
* Holds if `rhs` is a RHS of node `nd`.
|
||||||
@@ -802,6 +816,14 @@ module API {
|
|||||||
trackUseNode(use).flowsTo(call.getReceiver())
|
trackUseNode(use).flowsTo(call.getReceiver())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `superclass` is the superclass of `mod`.
|
||||||
|
*/
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate superclassNode(DataFlow::ModuleNode mod, DataFlow::Node superclass) {
|
||||||
|
superclass.asExpr().getExpr() = mod.getADeclaration().(ClassDeclaration).getSuperclassExpr()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||||
*/
|
*/
|
||||||
@@ -813,38 +835,35 @@ module API {
|
|||||||
useRoot(lbl, ref)
|
useRoot(lbl, ref)
|
||||||
or
|
or
|
||||||
exists(DataFlow::Node node, DataFlow::Node src |
|
exists(DataFlow::Node node, DataFlow::Node src |
|
||||||
pred = MkUse(src) and
|
use(pred, src) and
|
||||||
trackUseNode(src).flowsTo(node) and
|
trackUseNode(src).flowsTo(node) and
|
||||||
useStep(lbl, node, ref)
|
useStep(lbl, node, ref)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(DataFlow::Node callback |
|
exists(DataFlow::Node callback |
|
||||||
pred = MkDef(callback) and
|
def(pred, callback) and
|
||||||
parameterStep(lbl, trackDefNode(callback), ref)
|
parameterStep(lbl, trackDefNode(callback), ref)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(DataFlow::Node predNode, DataFlow::Node succNode |
|
exists(DataFlow::Node predNode, DataFlow::Node succNode |
|
||||||
def(pred, predNode) and
|
def(pred, predNode) and
|
||||||
def(succ, succNode) and
|
succ = MkDef(succNode) and
|
||||||
defStep(lbl, trackDefNode(predNode), succNode)
|
defStep(lbl, trackDefNode(predNode), succNode)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
// `pred` is a use of class A
|
exists(DataFlow::Node predNode, DataFlow::Node superclassNode, DataFlow::ModuleNode mod |
|
||||||
// `succ` is a use of class B
|
use(pred, predNode) and
|
||||||
// there exists a class declaration B < A
|
trackUseNode(predNode).flowsTo(superclassNode) and
|
||||||
exists(ClassDeclaration c, DataFlow::Node a, DataFlow::Node b |
|
superclassNode(mod, superclassNode) and
|
||||||
use(pred, a) and
|
succ = MkModuleObject(mod) and
|
||||||
use(succ, b) and
|
|
||||||
b.asExpr().getExpr().(ConstantReadAccess).getAQualifiedName() = c.getAQualifiedName() and
|
|
||||||
pragma[only_bind_into](c).getSuperclassExpr() = a.asExpr().getExpr() and
|
|
||||||
lbl = Label::subclass()
|
lbl = Label::subclass()
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(DataFlow::CallNode call |
|
exists(DataFlow::CallNode call |
|
||||||
// from receiver to method call node
|
// from receiver to method call node
|
||||||
exists(DataFlow::Node receiver |
|
exists(DataFlow::Node receiver |
|
||||||
pred = MkUse(receiver) and
|
use(pred, receiver) and
|
||||||
useNodeReachesReceiver(receiver, call) and
|
useNodeReachesReceiver(receiver, call) and
|
||||||
lbl = Label::method(call.getMethodName()) and
|
lbl = Label::method(call.getMethodName()) and
|
||||||
succ = MkMethodAccessNode(call)
|
succ = MkMethodAccessNode(call)
|
||||||
|
|||||||
@@ -1169,13 +1169,12 @@ private module ArrayLiteralDesugar {
|
|||||||
child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements()))
|
child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements()))
|
||||||
or
|
or
|
||||||
parent = TMethodCallSynth(al, -1, _, _, _) and
|
parent = TMethodCallSynth(al, -1, _, _, _) and
|
||||||
(
|
|
||||||
i = 0 and
|
i = 0 and
|
||||||
child = SynthChild(ConstantReadAccessKind("::Array"))
|
child = SynthChild(ConstantReadAccessKind("::Array"))
|
||||||
or
|
or
|
||||||
|
parent = TMethodCallSynth(al, -1, _, _, _) and
|
||||||
child = childRef(al.getElement(i - 1))
|
child = childRef(al.getElement(i - 1))
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1212,13 +1211,12 @@ private module HashLiteralDesugar {
|
|||||||
child = SynthChild(MethodCallKind("[]", false, hl.getNumberOfElements()))
|
child = SynthChild(MethodCallKind("[]", false, hl.getNumberOfElements()))
|
||||||
or
|
or
|
||||||
parent = TMethodCallSynth(hl, -1, _, _, _) and
|
parent = TMethodCallSynth(hl, -1, _, _, _) and
|
||||||
(
|
|
||||||
i = 0 and
|
i = 0 and
|
||||||
child = SynthChild(ConstantReadAccessKind("::Hash"))
|
child = SynthChild(ConstantReadAccessKind("::Hash"))
|
||||||
or
|
or
|
||||||
|
parent = TMethodCallSynth(hl, -1, _, _, _) and
|
||||||
child = childRef(hl.getElement(i - 1))
|
child = childRef(hl.getElement(i - 1))
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -890,6 +890,9 @@ class ModuleNode instanceof Module {
|
|||||||
/** Gets a constant or `self` variable that refers to this module. */
|
/** Gets a constant or `self` variable that refers to this module. */
|
||||||
LocalSourceNode getAnImmediateReference() {
|
LocalSourceNode getAnImmediateReference() {
|
||||||
result.asExpr().getExpr() = super.getAnImmediateReference()
|
result.asExpr().getExpr() = super.getAnImmediateReference()
|
||||||
|
or
|
||||||
|
// Include 'self' parameters; these are not expressions and so not found by the case above
|
||||||
|
result = this.getAnOwnModuleSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -127,11 +127,28 @@ getModuleLevelSelf
|
|||||||
getAnImmediateReference
|
getAnImmediateReference
|
||||||
| file://:0:0:0:0 | Array | tst.rb:59:15:59:21 | Array |
|
| file://:0:0:0:0 | Array | tst.rb:59:15:59:21 | Array |
|
||||||
| file://:0:0:0:0 | Hash | tst.rb:60:14:60:45 | Hash |
|
| file://:0:0:0:0 | Hash | tst.rb:60:14:60:45 | Hash |
|
||||||
|
| tst.rb:1:1:6:3 | C1 | tst.rb:1:1:6:3 | self (C1) |
|
||||||
| tst.rb:1:1:6:3 | C1 | tst.rb:8:12:8:13 | C1 |
|
| tst.rb:1:1:6:3 | C1 | tst.rb:8:12:8:13 | C1 |
|
||||||
|
| tst.rb:8:1:11:3 | C2 | tst.rb:8:1:11:3 | self (C2) |
|
||||||
| tst.rb:8:1:11:3 | C2 | tst.rb:27:12:27:13 | C2 |
|
| tst.rb:8:1:11:3 | C2 | tst.rb:27:12:27:13 | C2 |
|
||||||
|
| tst.rb:13:1:18:3 | Mixin | tst.rb:13:1:18:3 | self (Mixin) |
|
||||||
|
| tst.rb:13:1:18:3 | Mixin | tst.rb:16:5:17:7 | self in m1s |
|
||||||
| tst.rb:13:1:18:3 | Mixin | tst.rb:28:13:28:17 | Mixin |
|
| tst.rb:13:1:18:3 | Mixin | tst.rb:28:13:28:17 | Mixin |
|
||||||
|
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:20:1:25:3 | self (Mixin2) |
|
||||||
|
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:23:5:24:7 | self in m2s |
|
||||||
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:29:13:29:18 | Mixin2 |
|
| tst.rb:20:1:25:3 | Mixin2 | tst.rb:29:13:29:18 | Mixin2 |
|
||||||
|
| tst.rb:27:1:35:3 | C3 | tst.rb:27:1:35:3 | self (C3) |
|
||||||
|
| tst.rb:27:1:35:3 | C3 | tst.rb:32:9:33:11 | self in c3_self1 |
|
||||||
|
| tst.rb:27:1:35:3 | C3 | tst.rb:37:1:38:3 | self in c3_self2 |
|
||||||
| tst.rb:27:1:35:3 | C3 | tst.rb:37:5:37:6 | C3 |
|
| tst.rb:27:1:35:3 | C3 | tst.rb:37:5:37:6 | C3 |
|
||||||
|
| tst.rb:40:1:47:3 | N1 | tst.rb:40:1:47:3 | self (N1) |
|
||||||
|
| tst.rb:41:5:42:7 | N1::XY1 | tst.rb:41:5:42:7 | self (XY1) |
|
||||||
|
| tst.rb:43:5:46:7 | N1::N2 | tst.rb:43:5:46:7 | self (N2) |
|
||||||
|
| tst.rb:44:9:45:11 | N1::N2::XY2 | tst.rb:44:9:45:11 | self (XY2) |
|
||||||
|
| tst.rb:49:1:51:3 | N2 | tst.rb:49:1:51:3 | self (N2) |
|
||||||
|
| tst.rb:49:1:51:3 | N2 | tst.rb:52:1:55:3 | self (N2) |
|
||||||
|
| tst.rb:53:5:54:7 | N2::XY3 | tst.rb:53:5:54:7 | self (XY3) |
|
||||||
|
| tst.rb:57:1:62:3 | Nodes | tst.rb:57:1:62:3 | self (Nodes) |
|
||||||
getOwnInstanceMethod
|
getOwnInstanceMethod
|
||||||
| tst.rb:1:1:6:3 | C1 | c1 | tst.rb:2:5:5:7 | c1 |
|
| tst.rb:1:1:6:3 | C1 | c1 | tst.rb:2:5:5:7 | c1 |
|
||||||
| tst.rb:8:1:11:3 | C2 | c2 | tst.rb:9:5:10:7 | c2 |
|
| tst.rb:8:1:11:3 | C2 | c2 | tst.rb:9:5:10:7 | c2 |
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ void Log::configure() {
|
|||||||
// as we are configuring logging right now, we collect problems and log them at the end
|
// as we are configuring logging right now, we collect problems and log them at the end
|
||||||
auto problems = collectSeverityRulesAndReturnProblems("CODEQL_EXTRACTOR_SWIFT_LOG_LEVELS");
|
auto problems = collectSeverityRulesAndReturnProblems("CODEQL_EXTRACTOR_SWIFT_LOG_LEVELS");
|
||||||
if (text || binary) {
|
if (text || binary) {
|
||||||
std::filesystem::path logFile = getEnvOr("CODEQL_EXTRACTOR_SWIFT_LOG_DIR", ".");
|
std::filesystem::path logFile = getEnvOr("CODEQL_EXTRACTOR_SWIFT_LOG_DIR", "extractor-out/log");
|
||||||
|
logFile /= "swift";
|
||||||
logFile /= logRootName;
|
logFile /= logRootName;
|
||||||
logFile /= std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
logFile /= std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|||||||
@@ -177,9 +177,11 @@ codeql::TrapDomain invocationTrapDomain(codeql::SwiftExtractorState& state) {
|
|||||||
|
|
||||||
codeql::SwiftExtractorConfiguration configure(int argc, char** argv) {
|
codeql::SwiftExtractorConfiguration configure(int argc, char** argv) {
|
||||||
codeql::SwiftExtractorConfiguration configuration{};
|
codeql::SwiftExtractorConfiguration configuration{};
|
||||||
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
|
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", "extractor-out/trap/swift");
|
||||||
configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
|
configuration.sourceArchiveDir =
|
||||||
configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", ".");
|
getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", "extractor-out/src");
|
||||||
|
configuration.scratchDir =
|
||||||
|
getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", "extractor-out/working");
|
||||||
configuration.frontendOptions.assign(argv + 1, argv + argc);
|
configuration.frontendOptions.assign(argv + 1, argv + argc);
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ ql/lib/codeql/swift/elements/decl/SubscriptDeclConstructor.qll 3a88617b41f96827c
|
|||||||
ql/lib/codeql/swift/elements/decl/TopLevelCodeDeclConstructor.qll 6920a4e7aec45ae2a561cef95b9082b861f81c16c259698541f317481645e194 4bd65820b93a5ec7332dd1bbf59326fc19b77e94c122ad65d41393c84e6ac581
|
ql/lib/codeql/swift/elements/decl/TopLevelCodeDeclConstructor.qll 6920a4e7aec45ae2a561cef95b9082b861f81c16c259698541f317481645e194 4bd65820b93a5ec7332dd1bbf59326fc19b77e94c122ad65d41393c84e6ac581
|
||||||
ql/lib/codeql/swift/elements/decl/TypeAliasDecl.qll 984c5802c35e595388f7652cef1a50fb963b32342ab4f9d813b7200a0e6a37ca 630dc9cbf20603855c599a9f86037ba0d889ad3d2c2b6f9ac17508d398bff9e3
|
ql/lib/codeql/swift/elements/decl/TypeAliasDecl.qll 984c5802c35e595388f7652cef1a50fb963b32342ab4f9d813b7200a0e6a37ca 630dc9cbf20603855c599a9f86037ba0d889ad3d2c2b6f9ac17508d398bff9e3
|
||||||
ql/lib/codeql/swift/elements/decl/TypeAliasDeclConstructor.qll ba70bb69b3a14283def254cc1859c29963838f624b3f1062a200a8df38f1edd5 96ac51d1b3156d4139e583f7f803e9eb95fe25cc61c12986e1b2972a781f9c8b
|
ql/lib/codeql/swift/elements/decl/TypeAliasDeclConstructor.qll ba70bb69b3a14283def254cc1859c29963838f624b3f1062a200a8df38f1edd5 96ac51d1b3156d4139e583f7f803e9eb95fe25cc61c12986e1b2972a781f9c8b
|
||||||
ql/lib/codeql/swift/elements/decl/ValueDecl.qll 1b7d8eeb17be4bdbabc156cb0689641ed4f9e697e334d2d14f423ed3d1a419f6 e3056cf6a883da2737cb220a89499a9e3977eb1c56b9e1d2f41a56b71a0c29f9
|
|
||||||
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExpr.qll 39b856c89b8aff769b75051fd9e319f2d064c602733eaa6fed90d8f626516306 a87738539276438cef63145461adf25309d1938cfac367f53f53d33db9b12844
|
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExpr.qll 39b856c89b8aff769b75051fd9e319f2d064c602733eaa6fed90d8f626516306 a87738539276438cef63145461adf25309d1938cfac367f53f53d33db9b12844
|
||||||
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExprConstructor.qll 7d70e7c47a9919efcb1ebcbf70e69cab1be30dd006297b75f6d72b25ae75502a e7a741c42401963f0c1da414b3ae779adeba091e9b8f56c9abf2a686e3a04d52
|
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExprConstructor.qll 7d70e7c47a9919efcb1ebcbf70e69cab1be30dd006297b75f6d72b25ae75502a e7a741c42401963f0c1da414b3ae779adeba091e9b8f56c9abf2a686e3a04d52
|
||||||
ql/lib/codeql/swift/elements/expr/AbstractClosureExpr.qll 4027b51a171387332f96cb7b78ca87a6906aec76419938157ac24a60cff16519 400790fe643585ad39f40c433eff8934bbe542d140b81341bca3b6dfc5b22861
|
ql/lib/codeql/swift/elements/expr/AbstractClosureExpr.qll 4027b51a171387332f96cb7b78ca87a6906aec76419938157ac24a60cff16519 400790fe643585ad39f40c433eff8934bbe542d140b81341bca3b6dfc5b22861
|
||||||
|
|||||||
@@ -361,3 +361,52 @@ module MergePathGraph<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||||
|
*/
|
||||||
|
module MergePathGraph3<
|
||||||
|
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||||
|
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||||
|
{
|
||||||
|
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||||
|
|
||||||
|
private module Merged =
|
||||||
|
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||||
|
|
||||||
|
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||||
|
class PathNode instanceof Merged::PathNode {
|
||||||
|
/** Gets this as a projection on the first given `PathGraph`. */
|
||||||
|
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the second given `PathGraph`. */
|
||||||
|
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets this as a projection on the third given `PathGraph`. */
|
||||||
|
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||||
|
|
||||||
|
/** Gets a textual representation of this element. */
|
||||||
|
string toString() { result = super.toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this element is at the specified location.
|
||||||
|
* The location spans column `startcolumn` of line `startline` to
|
||||||
|
* column `endcolumn` of line `endline` in file `filepath`.
|
||||||
|
* For more information, see
|
||||||
|
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||||
|
*/
|
||||||
|
predicate hasLocationInfo(
|
||||||
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
|
) {
|
||||||
|
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the underlying `Node`. */
|
||||||
|
Node getNode() { result = super.getNode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||||
|
*/
|
||||||
|
module PathGraph = Merged::PathGraph;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
private import codeql.swift.generated.AstNode
|
private import codeql.swift.generated.AstNode
|
||||||
private import codeql.swift.elements.decl.AbstractFunctionDecl
|
private import codeql.swift.elements.decl.AbstractFunctionDecl
|
||||||
private import codeql.swift.elements.decl.Decl
|
private import codeql.swift.elements.decl.Decl
|
||||||
|
private import codeql.swift.elements.expr.AbstractClosureExpr
|
||||||
|
private import codeql.swift.elements.Callable
|
||||||
private import codeql.swift.generated.ParentChild
|
private import codeql.swift.generated.ParentChild
|
||||||
|
|
||||||
private module Cached {
|
private module Cached {
|
||||||
@@ -21,10 +23,59 @@ private module Cached {
|
|||||||
AbstractFunctionDecl getEnclosingFunction(AstNode ast) {
|
AbstractFunctionDecl getEnclosingFunction(AstNode ast) {
|
||||||
result = getEnclosingFunctionStep*(getEnclosingDecl(ast))
|
result = getEnclosingFunctionStep*(getEnclosingDecl(ast))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Element getEnclosingClosureStep(Element e) {
|
||||||
|
not e instanceof Callable and
|
||||||
|
result = getImmediateParent(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cached
|
||||||
|
AbstractClosureExpr getEnclosingClosure(AstNode ast) {
|
||||||
|
result = getEnclosingClosureStep*(getImmediateParent(ast))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node in the abstract syntax tree.
|
||||||
|
*/
|
||||||
class AstNode extends Generated::AstNode {
|
class AstNode extends Generated::AstNode {
|
||||||
|
/**
|
||||||
|
* Gets the nearest function definition that contains this AST node, if any.
|
||||||
|
* This includes functions, methods, (de)initializers, and accessors, but not closures.
|
||||||
|
*
|
||||||
|
* For example, in the following code, the AST node for `n + 1` has `foo` as its
|
||||||
|
* enclosing function (via `getEnclosingFunction`), whereas its enclosing callable is
|
||||||
|
* the closure `{(n : Int) in n + 1 }` (via `getEnclosingCallable`):
|
||||||
|
*
|
||||||
|
* ```swift
|
||||||
|
* func foo() {
|
||||||
|
* var f = { (n : Int) in n + 1 }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
final AbstractFunctionDecl getEnclosingFunction() { result = Cached::getEnclosingFunction(this) }
|
final AbstractFunctionDecl getEnclosingFunction() { result = Cached::getEnclosingFunction(this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nearest declaration that contains this AST node, if any.
|
||||||
|
*/
|
||||||
final Decl getEnclosingDecl() { result = Cached::getEnclosingDecl(this) }
|
final Decl getEnclosingDecl() { result = Cached::getEnclosingDecl(this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nearest `Callable` that contains this AST node, if any.
|
||||||
|
* This includes (auto)closures, functions, methods, (de)initializers, and accessors.
|
||||||
|
*
|
||||||
|
* For example, in the following code, the AST node for `n + 1` has the closure
|
||||||
|
* `{(n : Int) in n + 1 }` as its enclosing callable.
|
||||||
|
*
|
||||||
|
* ```swift
|
||||||
|
* func foo() {
|
||||||
|
* var f = { (n : Int) in n + 1 }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
final Callable getEnclosingCallable() {
|
||||||
|
if exists(Cached::getEnclosingClosure(this))
|
||||||
|
then result = Cached::getEnclosingClosure(this)
|
||||||
|
else result = Cached::getEnclosingFunction(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
private import codeql.swift.generated.decl.CapturedDecl
|
private import codeql.swift.generated.decl.CapturedDecl
|
||||||
|
private import codeql.swift.elements.Callable
|
||||||
|
private import codeql.swift.elements.expr.DeclRefExpr
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A captured variable or function parameter in the scope of a closure.
|
||||||
|
*/
|
||||||
class CapturedDecl extends Generated::CapturedDecl {
|
class CapturedDecl extends Generated::CapturedDecl {
|
||||||
override string toString() { result = this.getDecl().toString() }
|
override string toString() { result = this.getDecl().toString() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the closure or function that captures this variable.
|
||||||
|
*/
|
||||||
|
Callable getScope() { result.getACapture() = this }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an access to this capture within the scope of its closure.
|
||||||
|
*/
|
||||||
|
DeclRefExpr getAnAccess() {
|
||||||
|
result.getEnclosingCallable() = this.getScope() and
|
||||||
|
result.getDecl() = this.getDecl()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,23 @@
|
|||||||
// generated by codegen/codegen.py, remove this comment if you wish to edit this file
|
|
||||||
private import codeql.swift.generated.decl.ValueDecl
|
private import codeql.swift.generated.decl.ValueDecl
|
||||||
|
private import codeql.swift.elements.decl.CapturedDecl
|
||||||
|
private import codeql.swift.elements.expr.DeclRefExpr
|
||||||
|
|
||||||
class ValueDecl extends Generated::ValueDecl { }
|
/**
|
||||||
|
* A declaration that introduces a value with a type.
|
||||||
|
*/
|
||||||
|
class ValueDecl extends Generated::ValueDecl {
|
||||||
|
/**
|
||||||
|
* Gets a capture of this declaration in the scope of a closure.
|
||||||
|
*/
|
||||||
|
CapturedDecl asCapturedDecl() { result.getDecl() = this }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this declaration is captured by a closure.
|
||||||
|
*/
|
||||||
|
predicate isCaptured() { exists(this.asCapturedDecl()) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an expression that references this declaration.
|
||||||
|
*/
|
||||||
|
DeclRefExpr getAnAccess() { result.getDecl() = this }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
private import codeql.swift.generated.decl.VarDecl
|
private import codeql.swift.generated.decl.VarDecl
|
||||||
private import codeql.swift.elements.expr.DeclRefExpr
|
|
||||||
private import codeql.swift.elements.decl.Decl
|
private import codeql.swift.elements.decl.Decl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variable declaration.
|
||||||
|
*/
|
||||||
class VarDecl extends Generated::VarDecl {
|
class VarDecl extends Generated::VarDecl {
|
||||||
override string toString() { result = this.getName() }
|
override string toString() { result = this.getName() }
|
||||||
|
|
||||||
DeclRefExpr getAnAccess() { result.getDecl() = this }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A field declaration.
|
||||||
|
*/
|
||||||
class FieldDecl extends VarDecl {
|
class FieldDecl extends VarDecl {
|
||||||
FieldDecl() { this = any(Decl ctx).getAMember() }
|
FieldDecl() { this = any(Decl ctx).getAMember() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
private import codeql.swift.generated.expr.DeclRefExpr
|
private import codeql.swift.generated.expr.DeclRefExpr
|
||||||
|
private import codeql.swift.elements.decl.CapturedDecl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An expression that references or accesses a declaration.
|
||||||
|
*/
|
||||||
class DeclRefExpr extends Generated::DeclRefExpr {
|
class DeclRefExpr extends Generated::DeclRefExpr {
|
||||||
override string toString() {
|
override string toString() {
|
||||||
if exists(this.getDecl().toString())
|
if exists(this.getDecl().toString())
|
||||||
then result = this.getDecl().toString()
|
then result = this.getDecl().toString()
|
||||||
else result = "(unknown declaration)"
|
else result = "(unknown declaration)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the closure capture referenced by this expression, if any.
|
||||||
|
*/
|
||||||
|
CapturedDecl getCapturedDecl() { result.getAnAccess() = this }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this expression references a closure capture.
|
||||||
|
*/
|
||||||
|
predicate hasCapturedDecl() { exists(this.getCapturedDecl()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,41 @@
|
|||||||
| closures.swift:8:12:8:12 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:7:6:7:6 | x | isDirect: | yes | isEscaping: | no |
|
| closures.swift:8:12:8:12 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:7:6:7:6 | x | isDirect: | yes | isEscaping: | no |
|
||||||
| closures.swift:9:12:9:12 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:6:7:6:7 | y | isDirect: | yes | isEscaping: | no |
|
| closures.swift:9:12:9:12 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:6:7:6:7 | y | isDirect: | yes | isEscaping: | no |
|
||||||
|
| closures.swift:16:3:16:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
|
||||||
| closures.swift:17:5:17:5 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:15:7:15:7 | x | isDirect: | yes | isEscaping: | yes |
|
| closures.swift:17:5:17:5 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:15:7:15:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
| closures.swift:20:3:20:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
|
| closures.swift:24:3:24:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
|
||||||
| closures.swift:25:3:25:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
|
| closures.swift:31:11:31:11 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | no | isEscaping: | no |
|
||||||
|
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | no | isEscaping: | no |
|
||||||
|
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | yes | isEscaping: | no |
|
||||||
|
| closures.swift:32:17:32:17 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | yes | isEscaping: | no |
|
||||||
|
| closures.swift:39:20:39:20 | callback | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:36:21:36:58 | callback | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:42:35:42:35 | wrapper(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:38:5:40:5 | wrapper(_:) | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:52:18:52:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:54:13:54:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:61:18:61:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:63:13:63:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:71:3:71:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:71:14:71:14 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:73:13:73:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:85:18:85:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:88:9:88:9 | b() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:92:5:98:5 | b() | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:93:7:93:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:93:20:93:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:96:9:96:9 | a() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:84:5:90:5 | a() | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:111:15:111:15 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:111:27:111:27 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:115:5:115:5 | incrX | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:109:8:109:8 | incrX | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:130:25:130:25 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:133:24:133:24 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:132:22:132:22 | y | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:151:21:151:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:154:16:154:16 | g(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:3:165:3 | g(_:) | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:155:21:155:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:154:9:154:9 | next | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:155:34:155:34 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:160:21:160:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:163:16:163:16 | f(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:3:156:3 | f(_:) | isDirect: | no | isEscaping: | yes |
|
||||||
|
| closures.swift:164:21:164:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:163:9:163:9 | next | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:164:36:164:36 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
|
||||||
|
| closures.swift:195:3:195:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,211 @@
|
|||||||
func bar() -> String {
|
func hello() -> String {
|
||||||
return "Hello world!"
|
return "Hello world!"
|
||||||
}
|
}
|
||||||
|
|
||||||
func foo() {
|
func captureList() {
|
||||||
let y = 123
|
let y = 123
|
||||||
{ [x = bar()] () in
|
{ [x = hello()] () in
|
||||||
print(x)
|
print(x)
|
||||||
print(y) }()
|
print(y) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
var escape: (() -> ())? = nil
|
var escape: (() -> ())? = nil
|
||||||
|
|
||||||
func baz() {
|
func setEscape() {
|
||||||
var x = 0
|
var x = 0
|
||||||
func quux() {
|
escape = {
|
||||||
x += 1
|
x += 1
|
||||||
print(x)
|
print(x)
|
||||||
}
|
}
|
||||||
escape = quux
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func callEscape() {
|
func callEscape() {
|
||||||
baz()
|
setEscape()
|
||||||
escape?()
|
escape?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logical() -> Bool {
|
||||||
|
let f: ((Int) -> Int)? = { x in x + 1 }
|
||||||
|
let x: Int? = 42
|
||||||
|
return f != nil
|
||||||
|
&& (x != nil
|
||||||
|
&& f!(x!) == 43)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncTest() {
|
||||||
|
func withCallback(_ callback: @escaping (Int) async -> Int) {
|
||||||
|
@Sendable
|
||||||
|
func wrapper(_ x: Int) async -> Int {
|
||||||
|
return await callback(x + 1)
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
print("asyncTest():", await wrapper(40))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withCallback { x in
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func foo() -> Int {
|
||||||
|
var x = 1 // x is a non-escaping capture of f and r
|
||||||
|
let f = { y in x += y }
|
||||||
|
x += 40
|
||||||
|
let r = { x }
|
||||||
|
f(1)
|
||||||
|
return r() // 42
|
||||||
|
}
|
||||||
|
|
||||||
|
func bar() -> () -> Int {
|
||||||
|
var x = 1 // x is a non-escaping capture of f, escaping capture of r
|
||||||
|
let f = { y in x += y }
|
||||||
|
x += 40
|
||||||
|
let r = { x }
|
||||||
|
f(1)
|
||||||
|
return r // constantly 42
|
||||||
|
}
|
||||||
|
|
||||||
|
var g: ((Int) -> Void)? = nil
|
||||||
|
func baz() -> () -> Int {
|
||||||
|
var x = 1 // x is an escaping capture of g and r
|
||||||
|
g = { y in x += y } // closure escapes!
|
||||||
|
x += 40
|
||||||
|
let r = { x }
|
||||||
|
g!(1)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func quux() -> Int {
|
||||||
|
var y = 0
|
||||||
|
|
||||||
|
func f() -> () -> Void {
|
||||||
|
var x = 5
|
||||||
|
|
||||||
|
func a() {
|
||||||
|
y = 10*y + x
|
||||||
|
x -= 1
|
||||||
|
if x > 0 {
|
||||||
|
b()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func b() {
|
||||||
|
y = 10*y + 2*x
|
||||||
|
x -= 1
|
||||||
|
if x > 0 {
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = f()
|
||||||
|
a()
|
||||||
|
return y // 58341
|
||||||
|
}
|
||||||
|
|
||||||
|
func sharedCapture() -> Int {
|
||||||
|
let (incrX, getX) = {
|
||||||
|
var x = 0
|
||||||
|
return ({ x += 1 }, { x })
|
||||||
|
}()
|
||||||
|
|
||||||
|
let doubleIncrX = {
|
||||||
|
incrX()
|
||||||
|
incrX()
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleIncrX()
|
||||||
|
doubleIncrX()
|
||||||
|
return getX() // 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func sink(_ x: Int) { print("sink:", x) }
|
||||||
|
func source() -> Int { -1 }
|
||||||
|
|
||||||
|
func sharedCaptureMultipleWriters() {
|
||||||
|
var x = 123
|
||||||
|
|
||||||
|
let callSink = { sink(x) }
|
||||||
|
|
||||||
|
let makeSetter = { y in
|
||||||
|
let setter = { x = y }
|
||||||
|
return setter
|
||||||
|
}
|
||||||
|
|
||||||
|
let goodSetter = makeSetter(42)
|
||||||
|
let badSetter = makeSetter(source())
|
||||||
|
|
||||||
|
goodSetter()
|
||||||
|
callSink()
|
||||||
|
|
||||||
|
badSetter()
|
||||||
|
callSink()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reentrant() -> Int {
|
||||||
|
|
||||||
|
func f(_ x: Int) -> (Int) -> Int {
|
||||||
|
if x == 0 {
|
||||||
|
return { _ in x }
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = g(x - 1)
|
||||||
|
return { k in k*next(10*k) + x }
|
||||||
|
}
|
||||||
|
|
||||||
|
func g(_ x: Int) -> (Int) -> Int {
|
||||||
|
if x == 0 {
|
||||||
|
return { _ in x }
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = f(x - 1)
|
||||||
|
return { k in k*next(10*k) + 2*x }
|
||||||
|
}
|
||||||
|
|
||||||
|
let h = f(5)
|
||||||
|
|
||||||
|
let y = h(10)
|
||||||
|
|
||||||
|
return y // 10004003085
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
print("captureList():")
|
||||||
|
captureList() // Hello world! 123
|
||||||
|
|
||||||
|
print("callEscape():")
|
||||||
|
callEscape() // 1
|
||||||
|
|
||||||
|
print("logical():", logical()) // true
|
||||||
|
|
||||||
|
print("asyncTest():")
|
||||||
|
asyncTest() // 42
|
||||||
|
|
||||||
|
print("foo():", foo()) // 42
|
||||||
|
|
||||||
|
let a = bar()
|
||||||
|
let b = baz()
|
||||||
|
|
||||||
|
print("bar():", a(), a()) // 42 42
|
||||||
|
|
||||||
|
print("baz():", b(), b()) // 42 42
|
||||||
|
|
||||||
|
g!(1)
|
||||||
|
print("g!(1):", b(), b()) // 43 43
|
||||||
|
|
||||||
|
g!(1)
|
||||||
|
print("g!(1):", b(), b()) // 44 44
|
||||||
|
|
||||||
|
print("quux():", quux()) // 58341
|
||||||
|
|
||||||
|
print("sharedCapture():", sharedCapture()) // 4
|
||||||
|
|
||||||
|
print("sharedCaptureMultipleWriters():")
|
||||||
|
sharedCaptureMultipleWriters() // 42, -1
|
||||||
|
|
||||||
|
print("reentrant():", reentrant()) // 10004003085
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user