Merge branch 'main' into kaspersv/prevent-python-join-order-regression

This commit is contained in:
Kasper Svendsen
2023-04-24 13:35:07 +02:00
committed by GitHub
43 changed files with 2204 additions and 482 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 { }

View File

@@ -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

View File

@@ -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()

View File

@@ -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 = ""

View File

@@ -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()

View File

@@ -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"

View File

@@ -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.

View File

@@ -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
( (

View File

@@ -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

View File

@@ -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`
*/ */

View File

@@ -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>;

View File

@@ -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;
}

View File

@@ -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 = ""

View File

@@ -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

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);
}
} }

View File

@@ -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;
}

View File

@@ -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

Binary file not shown.

View File

@@ -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"

View File

@@ -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" }

View File

@@ -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)

View File

@@ -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))
) )
)
} }
/** /**

View File

@@ -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;
}

View File

@@ -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()
} }
/** /**

View File

@@ -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 |

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)
}
} }

View File

@@ -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()
}
} }

View File

@@ -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 }
}

View File

@@ -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() }
} }

View File

@@ -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()) }
} }

View File

@@ -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 |

View File

@@ -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()