mirror of
https://github.com/github/codeql.git
synced 2025-12-21 19:26:31 +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) {
|
||||
// standard taint-tracking
|
||||
exists(
|
||||
TaintTrackingConfiguration c, DataFlow2::PathNode sourceNode, DataFlow2::PathNode sinkNode
|
||||
|
|
||||
exists(XssTracking::PathNode sourceNode, XssTracking::PathNode sinkNode |
|
||||
sourceNode = source.asDataFlowNode() and
|
||||
sinkNode = sink.asDataFlowNode() and
|
||||
c.hasFlowPath(sourceNode, sinkNode) and
|
||||
XssTracking::flowPath(sourceNode, sinkNode) and
|
||||
message =
|
||||
"is written to HTML or JavaScript" +
|
||||
any(string explanation |
|
||||
@@ -45,7 +43,7 @@ predicate xssFlow(XssNode source, XssNode sink, string message) {
|
||||
module PathGraph {
|
||||
/** Holds if `(pred,succ)` is an edge in the graph of data flow path explanations. */
|
||||
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
|
||||
succ.asDataFlowNode() = b
|
||||
)
|
||||
@@ -56,7 +54,7 @@ module PathGraph {
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(XssNode n, string key, string val) {
|
||||
DataFlow2::PathGraph::nodes(n.asDataFlowNode(), key, val)
|
||||
XssTracking::PathGraph::nodes(n.asDataFlowNode(), key, val)
|
||||
or
|
||||
xssFlow(n, n, _) and
|
||||
key = "semmle.label" and
|
||||
@@ -69,13 +67,13 @@ module PathGraph {
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
query predicate subpaths(XssNode arg, XssNode par, XssNode ret, XssNode out) {
|
||||
DataFlow2::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(), ret.asDataFlowNode(),
|
||||
out.asDataFlowNode())
|
||||
XssTracking::PathGraph::subpaths(arg.asDataFlowNode(), par.asDataFlowNode(),
|
||||
ret.asDataFlowNode(), out.asDataFlowNode())
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TXssNode =
|
||||
TXssDataFlowNode(DataFlow2::PathNode node) or
|
||||
TXssDataFlowNode(XssTracking::PathNode node) or
|
||||
TXssAspNode(AspInlineMember m)
|
||||
|
||||
/**
|
||||
@@ -90,21 +88,25 @@ class XssNode extends TXssNode {
|
||||
/** Gets the location of this node. */
|
||||
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. */
|
||||
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 {
|
||||
DataFlow2::PathNode node;
|
||||
XssTracking::PathNode node;
|
||||
|
||||
XssDataFlowNode() { this = TXssDataFlowNode(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() }
|
||||
|
||||
@@ -136,9 +138,11 @@ abstract class Source extends DataFlow::Node { }
|
||||
abstract class Sanitizer extends DataFlow::ExprNode { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `XssTracking` instead.
|
||||
*
|
||||
* A taint-tracking configuration for cross-site scripting (XSS) vulnerabilities.
|
||||
*/
|
||||
class TaintTrackingConfiguration extends TaintTracking2::Configuration {
|
||||
deprecated class TaintTrackingConfiguration extends TaintTracking2::Configuration {
|
||||
TaintTrackingConfiguration() { this = "XSSDataFlowConfiguration" }
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. */
|
||||
private class RemoteSource extends Source instanceof RemoteFlowSource { }
|
||||
|
||||
|
||||
@@ -12,38 +12,36 @@
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.Format
|
||||
import DataFlow::PathGraph
|
||||
import FormatInvalid::PathGraph
|
||||
|
||||
private class FormatConfiguration extends DataFlow::Configuration {
|
||||
FormatConfiguration() { this = "format" }
|
||||
module FormatInvalidConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
|
||||
|
||||
override predicate isSink(DataFlow::Node n) {
|
||||
exists(FormatCall c | n.asExpr() = c.getFormatExpr())
|
||||
}
|
||||
predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) }
|
||||
}
|
||||
|
||||
module FormatInvalid = DataFlow::Global<FormatInvalidConfig>;
|
||||
|
||||
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
|
||||
) {
|
||||
source.getNode().asExpr() = src and
|
||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
||||
FormatInvalid::flowPath(source, sink) and
|
||||
call.hasInsertions() and
|
||||
msg = "Invalid format string used in $@ formatting call." and
|
||||
callString = "this"
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
exists(int unused |
|
||||
source.getNode().asExpr() = src and
|
||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
||||
FormatInvalid::flowPath(source, sink) and
|
||||
unused = call.getASuppliedArgument() and
|
||||
not unused = src.getAnInsert() and
|
||||
not src.getValue() = "" and
|
||||
@@ -55,13 +53,13 @@ private predicate unusedArgument(
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
exists(int used, int supplied |
|
||||
source.getNode().asExpr() = src and
|
||||
sink.getNode().asExpr() = call.getFormatExpr() and
|
||||
any(FormatConfiguration conf).hasFlowPath(source, sink) and
|
||||
FormatInvalid::flowPath(source, sink) and
|
||||
used = src.getAnInsert() and
|
||||
supplied = call.getSuppliedArguments() and
|
||||
used >= supplied and
|
||||
@@ -71,8 +69,8 @@ private predicate missingArgument(
|
||||
}
|
||||
|
||||
from
|
||||
Element alert, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, Element extra1,
|
||||
string extra1String, Element extra2, string extra2String
|
||||
Element alert, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
|
||||
Element extra1, string extra1String, Element extra2, string extra2String
|
||||
where
|
||||
invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
|
||||
extra2 = extra1 and
|
||||
|
||||
@@ -24,10 +24,8 @@ private class ReturnNode extends DataFlow::ExprNode {
|
||||
}
|
||||
}
|
||||
|
||||
private class Conf extends DataFlow::Configuration {
|
||||
Conf() { this = "NoDisposeCallOnLocalIDisposable" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) {
|
||||
module DisposeCallOnLocalIDisposableConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) {
|
||||
node.asExpr() =
|
||||
any(LocalScopeDisposableCreation disposable |
|
||||
// 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
|
||||
node instanceof ReturnNode
|
||||
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() =
|
||||
any(LocalScopeDisposableCreation other | other.getAnArgument() = node1.asExpr())
|
||||
}
|
||||
|
||||
override predicate isBarrierOut(DataFlow::Node node) {
|
||||
this.isSink(node) and
|
||||
predicate isBarrierOut(DataFlow::Node node) {
|
||||
isSink(node) and
|
||||
not node instanceof ReturnNode
|
||||
}
|
||||
}
|
||||
|
||||
module DisposeCallOnLocalIDisposable = DataFlow::Global<DisposeCallOnLocalIDisposableConfig>;
|
||||
|
||||
/** Holds if `disposable` may not be disposed. */
|
||||
predicate mayNotBeDisposed(LocalScopeDisposableCreation disposable) {
|
||||
exists(Conf conf, DataFlow::ExprNode e |
|
||||
exists(DataFlow::ExprNode e |
|
||||
e.getExpr() = disposable and
|
||||
conf.isSource(e) and
|
||||
not exists(DataFlow::Node sink | conf.hasFlow(DataFlow::exprNode(disposable), sink) |
|
||||
DisposeCallOnLocalIDisposableConfig::isSource(e) and
|
||||
not exists(DataFlow::Node sink |
|
||||
DisposeCallOnLocalIDisposable::flow(DataFlow::exprNode(disposable), sink)
|
||||
|
|
||||
sink instanceof ReturnNode
|
||||
implies
|
||||
sink.getEnclosingCallable() = disposable.getEnclosingCallable()
|
||||
|
||||
@@ -16,18 +16,21 @@ import csharp
|
||||
import semmle.code.csharp.security.dataflow.flowsources.Stored
|
||||
import semmle.code.csharp.security.dataflow.XSSQuery
|
||||
import semmle.code.csharp.security.dataflow.XSSSinks
|
||||
import semmle.code.csharp.dataflow.DataFlow2
|
||||
import DataFlow2::PathGraph
|
||||
import StoredXss::PathGraph
|
||||
|
||||
class StoredTaintTrackingConfiguration extends TaintTrackingConfiguration {
|
||||
override predicate isSource(DataFlow2::Node source) { source instanceof StoredFlowSource }
|
||||
module StoredXssTrackingConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof StoredFlowSource }
|
||||
|
||||
predicate isSink = XssTrackingConfig::isSink/1;
|
||||
|
||||
predicate isBarrier = XssTrackingConfig::isBarrier/1;
|
||||
}
|
||||
|
||||
from
|
||||
StoredTaintTrackingConfiguration c, DataFlow2::PathNode source, DataFlow2::PathNode sink,
|
||||
string explanation
|
||||
module StoredXss = TaintTracking::Global<StoredXssTrackingConfig>;
|
||||
|
||||
from StoredXss::PathNode source, StoredXss::PathNode sink, string explanation
|
||||
where
|
||||
c.hasFlowPath(source, sink) and
|
||||
StoredXss::flowPath(source, sink) and
|
||||
if exists(sink.getNode().(Sink).explanation())
|
||||
then explanation = " (" + sink.getNode().(Sink).explanation() + ")"
|
||||
else explanation = ""
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.Test
|
||||
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
|
||||
import Random::InsecureRandomness::PathGraph
|
||||
|
||||
module Random {
|
||||
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.
|
||||
*/
|
||||
class TaintTrackingConfiguration extends TaintTracking::Configuration {
|
||||
TaintTrackingConfiguration() { this = "RandomDataFlowConfiguration" }
|
||||
module InsecureRandomnessConfig implements DataFlow::ConfigSig {
|
||||
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 }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// succ = array_or_indexer[pred] - use of random numbers in an index
|
||||
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. */
|
||||
class RandomSource extends Source {
|
||||
RandomSource() {
|
||||
@@ -112,10 +115,8 @@ module Random {
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
Random::TaintTrackingConfiguration randomTracking, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink
|
||||
where randomTracking.hasFlowPath(source, sink)
|
||||
from Random::InsecureRandomness::PathNode source, Random::InsecureRandomness::PathNode sink
|
||||
where Random::InsecureRandomness::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This uses a cryptographically insecure random number generated at $@ in a security context.",
|
||||
source.getNode(), source.getNode().toString()
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
import csharp
|
||||
import RequestForgery::RequestForgery
|
||||
import semmle.code.csharp.dataflow.DataFlow::DataFlow::PathGraph
|
||||
import RequestForgeryFlow::PathGraph
|
||||
|
||||
from RequestForgeryConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where c.hasFlowPath(source, sink)
|
||||
from RequestForgeryFlow::PathNode source, RequestForgeryFlow::PathNode sink
|
||||
where RequestForgeryFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "The URL of this request depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -24,9 +24,11 @@ module RequestForgery {
|
||||
abstract private class Barrier extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `RequestForgeryFlow` instead.
|
||||
*
|
||||
* 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" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -54,6 +56,40 @@ module RequestForgery {
|
||||
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
|
||||
* for Server Side Request Forgery(SSRF) Vulnerabilities.
|
||||
|
||||
@@ -36,7 +36,7 @@ where
|
||||
iResponse.getAppendMethod() = mc.getTarget() and
|
||||
isCookieWithSensitiveName(mc.getArgument(0)) and
|
||||
// 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`
|
||||
exists(DataFlow::Node creation, DataFlow::Node append |
|
||||
CookieOptionsTracking::flow(creation, append) and
|
||||
@@ -67,7 +67,7 @@ where
|
||||
// default is not configured or is not set to `Always`
|
||||
not getAValueForCookiePolicyProp("HttpOnly").getValue() = "1" and
|
||||
// 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
|
||||
isCookieWithSensitiveName(mc.getArgument(0)) and
|
||||
(
|
||||
|
||||
@@ -30,7 +30,7 @@ where
|
||||
getAValueForCookiePolicyProp("Secure").getValue() = "1"
|
||||
) and
|
||||
// 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
|
||||
exists(ObjectCreation oc |
|
||||
@@ -80,7 +80,7 @@ where
|
||||
or
|
||||
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
|
||||
// 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`
|
||||
exists(DataFlow::Node creation |
|
||||
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`
|
||||
*/
|
||||
|
||||
@@ -9,18 +9,16 @@ import semmle.code.csharp.frameworks.microsoft.AspNetCore
|
||||
* Holds if the expression is a variable with a sensitive name.
|
||||
*/
|
||||
predicate isCookieWithSensitiveName(Expr cookieExpr) {
|
||||
exists(AuthCookieNameConfiguration dataflow, DataFlow::Node sink |
|
||||
dataflow.hasFlowTo(sink) and
|
||||
exists(DataFlow::Node sink |
|
||||
AuthCookieName::flowTo(sink) and
|
||||
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 {
|
||||
AuthCookieNameConfiguration() { this = "AuthCookieNameConfiguration" }
|
||||
|
||||
private module AuthCookieNameConfig implements DataFlow::ConfigSig {
|
||||
private predicate isAuthVariable(Expr expr) {
|
||||
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) {
|
||||
exists(Call c | sink.asExpr() = c.getAnArgument())
|
||||
}
|
||||
predicate isSink(DataFlow::Node sink) { 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.
|
||||
*
|
||||
@@ -134,18 +135,22 @@ Expr getAValueForProp(ObjectCreation create, Assignment a, string 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`.
|
||||
*/
|
||||
class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||
deprecated class OnAppendCookieSecureTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||
OnAppendCookieSecureTrackingConfig() { this = "OnAppendCookieSecureTrackingConfig" }
|
||||
|
||||
override string propertyName() { result = "Secure" }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `OnAppendCookieHttpOnlyTracking` instead.
|
||||
*
|
||||
* Tracks if a callback used in `OnAppendCookie` sets `HttpOnly` to `true`.
|
||||
*/
|
||||
class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||
deprecated class OnAppendCookieHttpOnlyTrackingConfig extends OnAppendCookieTrackingConfig {
|
||||
OnAppendCookieHttpOnlyTrackingConfig() { this = "OnAppendCookieHttpOnlyTrackingConfig" }
|
||||
|
||||
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
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Config extends TaintTracking::Configuration {
|
||||
Config() { this = "Config" }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr().(MethodAccess).getCallee().getName() = "source"
|
||||
or
|
||||
sourceNode(n, "kotlinMadFlowTest")
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node n) {
|
||||
predicate isSink(DataFlow::Node n) {
|
||||
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
||||
or
|
||||
sinkNode(n, "kotlinMadFlowTest")
|
||||
}
|
||||
}
|
||||
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
|
||||
class InlineFlowTest extends InlineExpectationsTest {
|
||||
InlineFlowTest() { this = "HasFlowTest" }
|
||||
|
||||
@@ -26,7 +26,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "flow" and
|
||||
exists(DataFlow::Node sink, Config c | c.hasFlowTo(sink) |
|
||||
exists(DataFlow::Node sink | Flow::flowTo(sink) |
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
value = ""
|
||||
|
||||
@@ -19,18 +19,18 @@ query predicate superAccesses(
|
||||
enclosingType = enclosingCallable.getDeclaringType()
|
||||
}
|
||||
|
||||
class Config extends DataFlow::Configuration {
|
||||
Config() { this = "testconfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node x) {
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node x) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
from Config c, DataFlow::Node source, DataFlow::Node sink
|
||||
where c.hasFlow(source, sink)
|
||||
module Flow = DataFlow::Global<Config>;
|
||||
|
||||
from DataFlow::Node source, DataFlow::Node sink
|
||||
where Flow::flow(source, sink)
|
||||
select source, sink
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import Flow::PathGraph
|
||||
|
||||
class Config extends DataFlow::Configuration {
|
||||
Config() { this = "Config" }
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) { n.asExpr().(StringLiteral).getValue() = "taint" }
|
||||
|
||||
override predicate isSink(DataFlow::Node n) {
|
||||
predicate isSink(DataFlow::Node n) {
|
||||
n.asExpr().(Argument).getCall().getCallee().getName() = "sink"
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, Config c
|
||||
where c.hasFlowPath(source, sink)
|
||||
module Flow = DataFlow::Global<Config>;
|
||||
|
||||
from Flow::PathNode source, Flow::PathNode sink
|
||||
where Flow::flowPath(source, sink)
|
||||
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) {
|
||||
file.getAbsolutePath().matches("%src/test/%") or
|
||||
file.getAbsolutePath().matches("%/guava-tests/%") or
|
||||
file.getAbsolutePath().matches("%/guava-testlib/%")
|
||||
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
|
||||
not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
|
||||
}
|
||||
|
||||
private predicate isJdkInternal(J::CompilationUnit cu) {
|
||||
|
||||
@@ -1,284 +1,28 @@
|
||||
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 org.yaml.snakeyaml.composer.Composer;
|
||||
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;
|
||||
import com.semmle.extractor.yaml.YamlPopulator;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/** 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 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) {
|
||||
this.tolerateParseErrors = config.isTolerateParseErrors();
|
||||
}
|
||||
|
||||
private LineTable getLineTable() {
|
||||
if (lineTable == null) {
|
||||
lineTable = new LineTable(this.textualExtractor.getSource());
|
||||
}
|
||||
return lineTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseResultInfo extract(TextualExtractor textualExtractor) {
|
||||
this.textualExtractor = textualExtractor;
|
||||
locationManager = textualExtractor.getLocationManager();
|
||||
trapWriter = textualExtractor.getTrapwriter();
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
new YamlPopulator(textualExtractor.getExtractedFile(), textualExtractor.getSource(),
|
||||
textualExtractor.getTrapwriter(),
|
||||
this.tolerateParseErrors).extract();
|
||||
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
|
||||
* @description SimpleXMLRPCServer is vulnerable to DoS attacks from untrusted user input
|
||||
* @name SimpleXMLRPCServer denial of service
|
||||
* @description SimpleXMLRPCServer is vulnerable to denial of service attacks from untrusted user input
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @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"
|
||||
chrono = "0.4.24"
|
||||
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"}
|
||||
clap = { version = "4.2", features = ["derive"] }
|
||||
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"
|
||||
regex = "1.7.3"
|
||||
regex = "1.8.1"
|
||||
codeql-extractor = { path = "../../shared/tree-sitter-extractor" }
|
||||
|
||||
@@ -359,6 +359,11 @@ module API {
|
||||
Location getLocation() {
|
||||
result = this.getInducingNode().getLocation()
|
||||
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
|
||||
// parameters are zero.
|
||||
not exists(this.getInducingNode()) and
|
||||
@@ -601,7 +606,9 @@ module API {
|
||||
/** A use of an API member at the node `nd`. */
|
||||
MkUse(DataFlow::Node nd) { isUse(nd) } or
|
||||
/** 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) {
|
||||
result = read.getModule().getQualifiedName() and
|
||||
@@ -684,7 +691,14 @@ module API {
|
||||
* Holds if `ref` is a use of node `nd`.
|
||||
*/
|
||||
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`.
|
||||
@@ -802,6 +816,14 @@ module API {
|
||||
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`.
|
||||
*/
|
||||
@@ -813,38 +835,35 @@ module API {
|
||||
useRoot(lbl, ref)
|
||||
or
|
||||
exists(DataFlow::Node node, DataFlow::Node src |
|
||||
pred = MkUse(src) and
|
||||
use(pred, src) and
|
||||
trackUseNode(src).flowsTo(node) and
|
||||
useStep(lbl, node, ref)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node callback |
|
||||
pred = MkDef(callback) and
|
||||
def(pred, callback) and
|
||||
parameterStep(lbl, trackDefNode(callback), ref)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node predNode, DataFlow::Node succNode |
|
||||
def(pred, predNode) and
|
||||
def(succ, succNode) and
|
||||
succ = MkDef(succNode) and
|
||||
defStep(lbl, trackDefNode(predNode), succNode)
|
||||
)
|
||||
or
|
||||
// `pred` is a use of class A
|
||||
// `succ` is a use of class B
|
||||
// there exists a class declaration B < A
|
||||
exists(ClassDeclaration c, DataFlow::Node a, DataFlow::Node b |
|
||||
use(pred, a) and
|
||||
use(succ, b) and
|
||||
b.asExpr().getExpr().(ConstantReadAccess).getAQualifiedName() = c.getAQualifiedName() and
|
||||
pragma[only_bind_into](c).getSuperclassExpr() = a.asExpr().getExpr() and
|
||||
exists(DataFlow::Node predNode, DataFlow::Node superclassNode, DataFlow::ModuleNode mod |
|
||||
use(pred, predNode) and
|
||||
trackUseNode(predNode).flowsTo(superclassNode) and
|
||||
superclassNode(mod, superclassNode) and
|
||||
succ = MkModuleObject(mod) and
|
||||
lbl = Label::subclass()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode call |
|
||||
// from receiver to method call node
|
||||
exists(DataFlow::Node receiver |
|
||||
pred = MkUse(receiver) and
|
||||
use(pred, receiver) and
|
||||
useNodeReachesReceiver(receiver, call) and
|
||||
lbl = Label::method(call.getMethodName()) and
|
||||
succ = MkMethodAccessNode(call)
|
||||
|
||||
@@ -1169,13 +1169,12 @@ private module ArrayLiteralDesugar {
|
||||
child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements()))
|
||||
or
|
||||
parent = TMethodCallSynth(al, -1, _, _, _) and
|
||||
(
|
||||
i = 0 and
|
||||
child = SynthChild(ConstantReadAccessKind("::Array"))
|
||||
or
|
||||
parent = TMethodCallSynth(al, -1, _, _, _) and
|
||||
child = childRef(al.getElement(i - 1))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1212,13 +1211,12 @@ private module HashLiteralDesugar {
|
||||
child = SynthChild(MethodCallKind("[]", false, hl.getNumberOfElements()))
|
||||
or
|
||||
parent = TMethodCallSynth(hl, -1, _, _, _) and
|
||||
(
|
||||
i = 0 and
|
||||
child = SynthChild(ConstantReadAccessKind("::Hash"))
|
||||
or
|
||||
parent = TMethodCallSynth(hl, -1, _, _, _) and
|
||||
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. */
|
||||
LocalSourceNode 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
|
||||
| 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 |
|
||||
| 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: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: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: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: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: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
|
||||
| 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 |
|
||||
|
||||
@@ -94,7 +94,8 @@ void Log::configure() {
|
||||
// as we are configuring logging right now, we collect problems and log them at the end
|
||||
auto problems = collectSeverityRulesAndReturnProblems("CODEQL_EXTRACTOR_SWIFT_LOG_LEVELS");
|
||||
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 /= std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
std::error_code ec;
|
||||
|
||||
@@ -177,9 +177,11 @@ codeql::TrapDomain invocationTrapDomain(codeql::SwiftExtractorState& state) {
|
||||
|
||||
codeql::SwiftExtractorConfiguration configure(int argc, char** argv) {
|
||||
codeql::SwiftExtractorConfiguration configuration{};
|
||||
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
|
||||
configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
|
||||
configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", ".");
|
||||
configuration.trapDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", "extractor-out/trap/swift");
|
||||
configuration.sourceArchiveDir =
|
||||
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);
|
||||
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/TypeAliasDecl.qll 984c5802c35e595388f7652cef1a50fb963b32342ab4f9d813b7200a0e6a37ca 630dc9cbf20603855c599a9f86037ba0d889ad3d2c2b6f9ac17508d398bff9e3
|
||||
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/AbiSafeConversionExprConstructor.qll 7d70e7c47a9919efcb1ebcbf70e69cab1be30dd006297b75f6d72b25ae75502a e7a741c42401963f0c1da414b3ae779adeba091e9b8f56c9abf2a686e3a04d52
|
||||
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.elements.decl.AbstractFunctionDecl
|
||||
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 module Cached {
|
||||
@@ -21,10 +23,59 @@ private module Cached {
|
||||
AbstractFunctionDecl getEnclosingFunction(AstNode 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 {
|
||||
/**
|
||||
* 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) }
|
||||
|
||||
/**
|
||||
* Gets the nearest declaration that contains this AST node, if any.
|
||||
*/
|
||||
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.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 {
|
||||
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.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.elements.expr.DeclRefExpr
|
||||
private import codeql.swift.elements.decl.Decl
|
||||
|
||||
/**
|
||||
* A variable declaration.
|
||||
*/
|
||||
class VarDecl extends Generated::VarDecl {
|
||||
override string toString() { result = this.getName() }
|
||||
|
||||
DeclRefExpr getAnAccess() { result.getDecl() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A field declaration.
|
||||
*/
|
||||
class FieldDecl extends VarDecl {
|
||||
FieldDecl() { this = any(Decl ctx).getAMember() }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
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 {
|
||||
override string toString() {
|
||||
if exists(this.getDecl().toString())
|
||||
then result = this.getDecl().toString()
|
||||
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: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: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: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: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: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!"
|
||||
}
|
||||
|
||||
func foo() {
|
||||
func captureList() {
|
||||
let y = 123
|
||||
{ [x = bar()] () in
|
||||
{ [x = hello()] () in
|
||||
print(x)
|
||||
print(y) }()
|
||||
}
|
||||
|
||||
var escape: (() -> ())? = nil
|
||||
|
||||
func baz() {
|
||||
func setEscape() {
|
||||
var x = 0
|
||||
func quux() {
|
||||
escape = {
|
||||
x += 1
|
||||
print(x)
|
||||
}
|
||||
escape = quux
|
||||
}
|
||||
|
||||
func callEscape() {
|
||||
baz()
|
||||
setEscape()
|
||||
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